Как записать значение в канал в go
Перейти к содержимому

Как записать значение в канал в go

  • автор:

Как записать значение в канал в go

Каналы (channels) представляют инструменты коммуникации между горутинами. Для определения канала применяется ключевое слово chan :

chan тип_элемента

После слова chan указывается тип данных, которые будут передаться с помощью канала. Например:

var intCh chan int

Здесь переменная intCh представляет канал, который передает данные типа int.

Для передачи данных в канал или, наоборот, из канала применяется операция

intCh 

В данном случае в канал посылается число 5. Получение данных из канала в переменную:

Если ранее в канал было отправлено число 5, то при выполнении операции

Стоит учитывать, что мы можем отправить в канал и получить из канала данные только того типа, который представляет канал. Так, в примере с каналом intCh это данные типа int.

Как правило, отправителем данных является одна горутина, а получателем - другая горутина.

При простом определении переменной канала она имеет значение nil , то есть по сути канал неинициализирован. Для инициализации применяется функция make() . В зависимости от определения емкости канала он может быть буферизированным или небуферизированным.

Небуфферизированные каналы

Для создания небуферизированного канала вызывается функция make() без указания емкости канала:

var intCh chan int = make(chan int) // канал для данных типа int strCh := make(chan string) // канал для данных типа string

Если канал пустой, то горутина-получатель блокируется, пока в канале не окажутся данные. Когда горутина-отправитель посылает данные, горутина-получатель получает эти данные и возобновляет работу.

Горутина-отправитель может отправлять данные только в пустой канал. Горутина-отправитель блокируется до тех пор, пока данные из канала не будут получены. Например:

package main import "fmt" func main() < intCh := make(chan int) go func()< fmt.Println("Go routine starts") intCh () fmt.Println( <-intCh) // получение данных из канала fmt.Println("The End") >

Через небуферизированный канал intCh горутина, представленная анонимной функцией, передает число 5:

intCh 

А функция main получает это число:

fmt.Println(<-intCh)

Общий ход выполнения программы выглядит следующим образом:

  1. Запускается функция main. Она создает канал intCh и запускает горутину в виде анонимной функции.
  2. Функция main продолжает выполняться и блокируется на строке fmt.Println( <-intCh) , пока не будут получены данные.
  3. Параллельно выполняется запущенная горутина в виде анонимной функции. В конце своего выполнения она отправляет даные через канал: intCh
  4. Функция main получает отправленные данные, деблокируется и продолжает свою работу.

В данном случае горутина определена в виде анонимной функции и поэтому она имеет доступ к окружению, в том числе к переменной intCh. Если же мы работаем с обычными функциями, то объекты каналов надо передавать через параметры:

package main import "fmt" func main() < intCh := make(chan int) go factorial(5, intCh) // вызов горутины fmt.Println(<-intCh) // получение данных из канала fmt.Println("The End") >func factorial(n int, ch chan int) < result := 1 for i := 1; i fmt.Println(n, "-", result) ch 

Обратите внимание, как определяется параметр, который представляет канал данных типа int: ch chan int . Консольный вывод данной программы:

5 - 120 120 The End

Таким образом, при использовании канала вызывающий поток - функция main ожидает завершения выполнения горутины.

Стоит отметить, что отправителем данных должна быть отдельно запускаемая горутина. Например, если мы определим отправление и получение данных через канал в самой функции main, то мы столкнемся с взаимоблокировкой:

package main import "fmt" func main() < intCh := make(chan int) intCh <- 10 // функция main блокируется fmt.Println(

Буферизированные каналы

Буферизированные каналы также создаются с помощью функции make() , только в качестве второго аргумента в функцию передается емкость канала. Если канал пуст, то получатель ждет, пока в канале появится хотя бы один элемент.

При отправке данных горутина-отправитель ожидает, пока в канале не освободится место для еще одного элемента и отправляет элемент, только тогда, когда в канале освобождается для него место.

package main import "fmt" func main() < intCh := make(chan int, 3) intCh 

В данном случае отправителем и получателем данных является функция main. В ней создается канал из трех элементов, и последовательно отправляются три значения типа int.

В то же время в данном случае должно быть соответствие между количеством отправляемых и получаемых данных. Если в функции main будет одновременно отправлено значений больще, чем вмещает канал, то функция заблокируется:

package main import "fmt" func main() < intCh := make(chan int, 3) intCh <- 10 intCh <- 3 intCh <- 24 intCh <- 15 // блокировка - функция main ждет, когда освободится место в канале fmt.Println(

С помощью встроенных функций cap() и len() можно получить соответственно емкость и количество элементов в канале:

package main import "fmt" func main() < intCh := make(chan int, 3) intCh <- 10 fmt.Println(cap(intCh)) // 3 fmt.Println(len(intCh)) // 1 fmt.Println(

Однонаправленные каналы

В Go можно определить канал, как доступный только для отправки данных или только для получения данных.

Определение канала только для отправки данных:

var inCh chan

Определение канала только для получения данных:

var outCh 
package main import "fmt" func main() < intCh := make(chan int, 2) go factorial(5, intCh) fmt.Println(<-intCh) fmt.Println("The End") >func factorial(n int, ch chan ch

Здесь второй параметр функции factorial определен как канал, доступный только для отправки данных: ch chan

Возвращение канала

Канал может быть возвращаемым значением функции. Однако следует внимательно подходить к операциям записи и чтения в возвращаемом канале. Например:

package main import "fmt" func main() < fmt.Println("Start") // создание канала и получение из него данных fmt.Println(<-createChan(5)) // блокировка fmt.Println("End") >func createChan(n int) chan int < ch := make(chan int) // создаем канал ch 

И если все таки необходимо определить функцию, которая возвращает канал, то все операции чтения-записи в канал следует вынести в отдельную горутину:

package main import "fmt" func main() < fmt.Println("Start") // создание канала и получение из него данных fmt.Println(<-createChan(5)) // 5 fmt.Println("End") >func createChan(n int) chan int< ch := make(chan int) // создаем канал go func()< ch () // запускаем горутину return ch // возвращаем канал >

Описание

Каналы — это инструмент для обмена данными между горутинами.

Создать канал можно с помощью ключевого слова chan. Канал имеет тип. Передать в канал значение другого типа не получится.

package main 
import "fmt"
func main() <
var c chan int
fmt.Println(c)
>

В go каналы являются указателями

Чтение и запись в канал

Для передачи данных в канал или, наоборот, из канала применяется операция

myCh 

Чтение из канала:

myVar := 

Небуфферизированные каналы

Начнём сразу с примера:

package main
import "fmt"
func main() intCh := make(chan int)
go func() fmt.Println("go func() start")
intCh >()
fmt.Println( <-intCh) // получение данных из канала
fmt.Println("Main() end")
>
go func() start
100
Main() end

Общий ход выполнения программы выглядит следующим образом:

  1. Запуск функции main. Она создает канал intCh и запускает горутину в виде анонимной функции.
  2. Далее функция main продолжает выполняться и блокируется на строке fmt.Println( <-intCh), пока не будут получены данные из канала
  3. Параллельно выполняется запущенная горутина в виде анонимной функции. В процессе своего выполнения она отправляет данные через канал: intCh
  4. Функция main получает данные fmt.Println( <-intCh) и продолжает выполнение

Взаимная блокировка (deadlock)

package main
import "fmt"

func main()
intCh := make(chan int)
intCh fmt.Println(<-intCh)
>

Буферизированные каналы

Простыми словами, буферезированные каналы, то же самое, что и НЕ буферезированные, только имеют ёмкость, т.е. какое количество значений помещается в канале. Выполнение функции которая помещает в канал данные не блокируется, если в канале есть свободное место

package main
import "fmt"

func main()
intCh := make(chan int, 3)
intCh intCh intCh fmt.Println( <-intCh)
fmt.Println( <-intCh)
fmt.Println(<-intCh)
>
100
200
300

В данном примере отправитель и получатель функция main.

Если отправить в канал больше значений, чем он вмещает, выполнение функции будет заблокировано.

package main
import "fmt"

func main()
intCh := make(chan int, 3)
intCh intCh intCh intCh
fmt.Println( <-intCh)
fmt.Println("End main()")
>

Получить ёмкость и количество значений в канале

С помощью встроенных функций cap() и len() можно получить ёмкость и количество значений в канале

package main
import "fmt"
func main() intCh := make(chan int, 3)
intCh intCh fmt.Println(fmt.Sprintf("cap(): %d", cap(intCh)))
fmt.Println(fmt.Sprintf("len(): %d", len(intCh)))
fmt.Println(<-intCh)
>
cap(): 3
len(): 2
100

постоянная запись в канал

Реализовать постоянную запись данных в канал (главный поток). Реализовать набор из N воркеров, которые читают произвольные данные из канала и выводят в stdout. Я начал с того что хотел записать значение в канал и запустить тестово несколько воркеров которые это значение выведут

package main import ( "context" "log" "os/signal" "sync" "syscall" "time" ) func Worker(ch chan int64, ctx context.Context, wg *sync.WaitGroup) < for < select < case println(data, "saved") default: println( <-ch) >> > func main() < ch := make(chan int64) ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT) for i := 0; i < 5; i++ < go Worker(ch, ctx, nil) >for < select < case > > 

Отслеживать
задан 25 мая 2022 в 13:38
995 12 12 серебряных знаков 21 21 бронзовый знак

Значение из канала можно взять только один раз. Чтобы взять еще одно такое же значение - его надо снова передать в канал.

25 мая 2022 в 14:44

@u_mulder а как-то можно вообще запустить пул воркеров с одним значением? Как его прочитать и поместить разные горутины?

25 мая 2022 в 15:18

Не очень понятно - почему с одним-то значением? Изначально ж задача стоит - есть канал в которой постоянно кто-то пишет, и надо создать пул воркеров, которые читают инфу из канала. В каждое прочтение воркер получает текущее доступное значение из канала, то есть нет нигде указания что, все воркеры получают одно и то же значение. Пул воркеров и нужен чтобы разгребать канал по мере получения сообщений, а не для того чтобы всем воркерам выдать одно и то же. Это как мешки разгружать - все грузчики подходят к куче, хватают каждый по мешку и несут. А не раздается всем копия мешка.

25 мая 2022 в 16:18

@u_mulder ваш комментарий можно понять как "вы корректно сделали воркера, осталось только сымитировать запись из разных источников".))

25 мая 2022 в 16:32

не понятно — нужно одно значение сразу во всех горутинах или нет? У меня была такая задача и понимаю, почему так может быть нужно. но нужно ли вам?

Go: Каналы

В Go существует постулат: "Do not communicate by sharing memory; instead, share memory by communicating" (Не общайтесь разделением памяти. Разделяйте память через общение). Для безопасной коммуникации между горутинами используется специальный тип данных: chan (канал).

Как слайсы и мапы, каналы инициализируются с помощью функции make :

numCh := make(chan int) 
numCh := make(chan int) numCh  

Чтение из канала блокирует текущую горутину, пока не вернется значение:

package main import ( "fmt" ) func main() < numCh := make(chan int) 

Запись в канал так же блокирует текущую горутину, пока кто-то не прочтет значение.

Каналы также можно использовать для задачи из прошлого урока:

package main import ( "fmt" ) func main() < fmt.Println(maxSum([]int, []int)) // [10 20 50] > // суммирует значения каждого слайса nums и возвращает тот, который имеет наибольшую сумму func maxSum(nums1, nums2 []int) []int < // канал для результата первой суммы s1Ch := make(chan int) go sumParallel(nums1, s1Ch) // канал для результата второй суммы s2Ch := make(chan int) go sumParallel(nums2, s2Ch) // присваиваем результаты в переменные. Здесь программа будет заблокирована, пока не придут результаты из обоих каналов. s1, s2 := s2 < return nums1 >return nums2 > func sumParallel(nums []int, resCh chan int) < s := 0 for _, n := range nums < s += n >// результат суммы передаем в канал resCh

Иногда требуется запустить обработчика в отдельной горутине, который будет выполнять работу на протяжении всего цикла жизни программы. С помощью конструкции for range можно читать из канала до того момента, пока он не будет закрыт:

package main import ( "fmt" "time" ) func main() < // создаем канал, в который будем отправлять сообщения msgCh := make(chan string) // вызываем функцию асинхронно в горутине go printer(msgCh) msgCh func printer(msgCh chan string) < // читаем из канала, пока он открыт for msg := range msgCh < fmt.Println(msg) >fmt.Println("printer has finished") > 

Задание

Реализуйте функцию-воркера SumWorker(numsCh chan []int, sumCh chan int) , которая суммирует переданные числа из канала numsCh и передает результат в канал sumCh:

numsCh := make(chan []int) sumCh := make(chan int) go SumWorker(numsCh, sumCh) numsCh res :=  

Упражнение не проходит проверку — что делать? ��

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.

В моей среде код работает, а здесь нет ��

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя ��

Это нормально ��, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно ��

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.

Полезное

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *