Но я нахожу этот пример неясным.
Ответ 1
Чан является каналом в Голанге. Проще говоря, вы можете думать об этом как о коробке, в которую вы кладете предмет с одного конца, а затем выбираете его с другого конца.
Небуферизованные каналы
![enter image description here]()
Буферизованный канал
![enter image description here]()
Это небольшой код, который я написал для вас, чтобы понять каналы. Теперь измените порядок выполнения процедур go и посмотрите результаты. Каждый раз выходные данные могут отличаться.
package main
import (
"fmt"
"time"
)
func main() {
messages := make(chan int)
go func() {
time.Sleep(time.Second * 3)
messages <- 1
}()
go func() {
time.Sleep(time.Second * 2)
messages <- 2
}()
go func() {
time.Sleep(time.Second * 1)
messages <- 3
}()
go func() {
for i := range messages {
fmt.Println(i)
}
}()
go func() {
time.Sleep(time.Second * 1)
messages <- 4
}()
go func() {
time.Sleep(time.Second * 1)
messages <- 5
}()
time.Sleep(time.Second * 5)
}
Для лучшего понимания посетите этот блог, где описаны процедуры и каналы в графическом интерфейсе.
Посещение http://divan.github.io/posts/go_concurrency_visualize/
Ответ 2
Я думаю, что спецификации довольно ясно об этом. Spec: типы каналов:
Канал предоставляет механизм для одновременного выполнения функций для связи с помощью отправки и получения значений указанного типа элемента.
Если у вас есть несколько подпрограмм, которые выполняются одновременно, каналы предоставляют самый простой способ позволить подпрограммам общаться друг с другом.
Одним из способов связи может быть использование "разделяемой" переменной, которая видна обеим подпрограммам, но для этого потребуется надлежащая блокировка/синхронизированный доступ.
Вместо этого Go предпочитает каналы. Цитата из Effective Go: поделитесь, общаясь:
Не общайтесь, разделяя память; вместо этого делитесь памятью, общаясь.
Таким образом, вместо того, чтобы помещать сообщения в общий слайс, например, вы можете создать канал (видимый для обеих подпрограмм), и без какой-либо внешней синхронизации/блокировки, одна подпрограмма может отправлять сообщения (значения) через канал, а другая подпрограмма может получать им.
Только одна программа имеет доступ к значению в любой момент времени. Гонки данных не могут быть предусмотрены.
Таким образом, на самом деле любое количество подпрограмм может отправлять значения по одному и тому же каналу, и любое количество подпрограмм может получать значения из него, без какой-либо дальнейшей синхронизации. См. связанный вопрос для получения более подробной информации: Если я правильно использую каналы, нужно ли мне использовать мьютексы?
Пример канала
Давайте посмотрим на пример, где мы запускаем 2 дополнительные программы для одновременных вычислений. Мы передаем число первому, которое добавляет к нему 1 и доставляет результат по 2-му каналу. Вторая программа получит число, умножит его на 10 и доставит в канал результатов:
func AddOne(ch chan<- int, i int) {
i++
ch <- i
}
func MulBy10(ch <-chan int, resch chan<- int) {
i := <-ch
i *= 10
resch <- i
}
Вот как это можно назвать/использовать:
func main() {
ch := make(chan int)
resch := make(chan int)
go AddOne(ch, 9)
go MulBy10(ch, resch)
result := <-resch
fmt.Println("Result:", result)
}
Общение по каналам также заботится о горутинах, ожидающих друг друга. В этом примере это означает, что MulBy10()
будет ждать, пока AddOne()
доставит увеличенное число, а main()
будет ждать MulBy10()
перед печатью результата. Вывод, как ожидается (попробуйте на Go Playground):
Result: 100
Языковая поддержка
Существует несколько языковых конструкций, предназначенных для удобного использования каналов, например:
for ... range
на канале перебирает значения, полученные от канала, до тех пор, пока канал не будет закрыт.
- Оператор
select
может использоваться для перечисления нескольких операций канала, таких как отправка по каналу и прием из канала, и будет выбрана та, которая может продолжаться без блокировки (случайным образом, если есть несколько операций, которые может продолжить и заблокирует, если ни один не готов).
- Существует специальная форма оператора получения, которая позволяет вам проверить, был ли канал закрыт (помимо получения значения):
v, ok := <-ch
- Встроенная функция
len()
сообщает количество элементов в очереди (непрочитано); строительная cap()
функция возвращает емкость буфера канала.
Другое использование
Для более практического примера посмотрите, как можно использовать каналы для реализации рабочего пула. Аналогичным образом используется распределение ценностей от производителя к потребителю (ям).
Другой практический пример - реализация пула памяти с использованием буферизованных каналов.
И еще один практический пример - элегантная реализация брокера.
Канал часто используется для тайм-аута некоторой операции блокировки, используя канал, возвращаемый time.After()
, который "срабатывает" после указанной задержки/длительности ("срабатывает" означает, что на него будет отправлено значение). Посмотрите этот пример для демонстрации (попробуйте на Go Playground):
ch := make(chan int)
select {
case i := <-ch:
fmt.Println("Received:", i)
case <-time.After(time.Second):
fmt.Println("Timeout, no value received")
}
Его можно использовать для ожидания максимального количества времени для некоторого значения, но если другие процедуры не могут предоставить значение к этому времени, мы можем вместо этого решить сделать что-то еще.
Также особой формой общения может быть просто сигнализировать о завершении какой-либо операции (без фактической отправки каких-либо "полезных" данных). Такой случай может быть реализован каналом с любым типом элемента, например, chan int
и отправив на него любое значение, например, 0
. Но так как отправленное значение не содержит информации, вы можете объявить его как chan struct{}
. Или, что еще лучше, если вам нужна только однократная сигнализация, вы можете просто закрыть канал, который можно перехватить на другой стороне, используя for ... range
, или получить от него (так как прием с закрытого канала происходит немедленно, давая нулевое значение типа элемента). Также знайте, что, хотя канал может использоваться для этого вида сигнализации, есть лучшая альтернатива для этого: sync.WaitGroup
.
Дальнейшее чтение
Чтобы избежать неожиданного поведения, стоит знать об аксиомах канала: Как ведет себя неинициализированный канал?
Блог Go: делитесь памятью, общаясь
Блог Go: шаблоны параллелизма Go: конвейеры и отмена
Блог Go: расширенные шаблоны параллелизма Go
Ardan labs: природа каналов в движении