Как записать значение в канал в go
По умолчанию каналы не буферизованы, это означает, что они будут принимать отправления ( chan Буферизованные каналы принимают ограниченное количество значений без соответствующего приемника для этих значений.
package main
import "fmt"
func main()
Здесь мы создаем канал строк с буфером до 2 значений.
messages := make(chan string, 2)
Т.к. этот канал буферизирован, мы можем послать значения в канал без соответствующего одновременного получения.
messages "buffered" messages "channel"
Позже мы можем получить эти значения как обычно.
fmt.Println(messages) fmt.Println(messages) >
$ go run channel-buffering.go buffered channel
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 :=
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
Как записать значение в канал в 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)Общий ход выполнения программы выглядит следующим образом:
- Запускается функция main. Она создает канал intCh и запускает горутину в виде анонимной функции.
- Функция main продолжает выполняться и блокируется на строке fmt.Println( <-intCh) , пока не будут получены данные.
- Параллельно выполняется запущенная горутина в виде анонимной функции. В конце своего выполнения она отправляет даные через канал: intCh
- Функция 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 // возвращаем канал >постоянная запись в канал
Реализовать постоянную запись данных в канал (главный поток). Реализовать набор из 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
1,043 12 12 серебряных знаков 21 21 бронзовый знакЗначение из канала можно взять только один раз. Чтобы взять еще одно такое же значение - его надо снова передать в канал.
25 мая 2022 в 14:44
@u_mulder а как-то можно вообще запустить пул воркеров с одним значением? Как его прочитать и поместить разные горутины?
25 мая 2022 в 15:18
Не очень понятно - почему с одним-то значением? Изначально ж задача стоит - есть канал в которой постоянно кто-то пишет, и надо создать пул воркеров, которые читают инфу из канала. В каждое прочтение воркер получает текущее доступное значение из канала, то есть нет нигде указания что, все воркеры получают одно и то же значение. Пул воркеров и нужен чтобы разгребать канал по мере получения сообщений, а не для того чтобы всем воркерам выдать одно и то же. Это как мешки разгружать - все грузчики подходят к куче, хватают каждый по мешку и несут. А не раздается всем копия мешка.
25 мая 2022 в 16:18
@u_mulder ваш комментарий можно понять как "вы корректно сделали воркера, осталось только сымитировать запись из разных источников".))
25 мая 2022 в 16:32
не понятно — нужно одно значение сразу во всех горутинах или нет? У меня была такая задача и понимаю, почему так может быть нужно. но нужно ли вам?