Канали¶
Канал (channel) — це типізований канал зв'язку, що дозволяє горутинам спілкуватися та синхронізуватися, передаючи значення. Одна горутина надсилає; інша отримує. Канал безпечно обробляє передачу, тож вам не потрібні блокування, щоб переміщати дані між горутинами.
Девіз Go: не спілкуйтеся, ділячись пам'яттю; діліться пам'яттю, спілкуючись. Надавайте перевагу передаванню значень каналом перед захистом спільних змінних м'ютексом.
Створіть канал через make, надсилайте через ch <- v, отримуйте через
v := <-ch:
Поширене застосування — повернення результату з горутини: канал і доставляє значення, і синхронізує, тож спільна змінна не потрібна:
total := make(chan int)
go func() {
sum := 0
for _, n := range []int{1, 2, 3, 4, 5} {
sum += n
}
total <- sum // надіслати результат назад у main
}()
fmt.Println(<-total) // output: 15
Небуферизовані канали синхронізують¶
Канал вище небуферизований: надсилання блокується, доки інша горутина не буде готова отримати, і навпаки. Обмін — це рандеву: обидві сторони зустрічаються в одну мить. Це робить небуферизований канал інструментом синхронізації, а не просто трубою: отримання не може завершитися раніше за надсилання.
done := make(chan struct{})
go func() {
fmt.Println("working")
done <- struct{}{} // сигнал про завершення
}()
<-done // блокує, доки горутина не подасть сигнал
fmt.Println("finished")
// output:
// working
// finished
chan struct{} — це ідіоматичний канал «лише сигнал, без даних».
Буферизовані канали тримають значення¶
make(chan T, n) дає каналу буфер на n. Надсилання блокується лише коли
буфер повний; отримання блокується лише коли він порожній. Це
розчіплює відправника й отримувача під час сплесків.
jobs := make(chan string, 2) // невелика черга завдань
jobs <- "email #1" // не блокує — у буфері є місце
jobs <- "email #2" // не блокує — тепер повний
fmt.Println(len(jobs), cap(jobs)) // output: 2 2
fmt.Println(<-jobs, <-jobs) // output: email #1 email #2
len — це скільки значень буферизовано просто зараз; cap — розмір
буфера. Використовуйте буфер, коли свідомо хочете запас; за замовчуванням
беріть небуферизований, бо він безкоштовно дає синхронізацію.
Закриття каналу¶
close(ch) позначає, що більше значень не надсилатимуть. Отримувачі ще
можуть вичерпати те, що буферизовано, а потім отримають нульове значення.
Отримання у формі comma-ok відрізняє справжнє значення від «закрито й
порожньо»:
ch := make(chan int, 2)
ch <- 10
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // output: 10 true — справжнє значення
v, ok = <-ch
fmt.Println(v, ok) // output: 0 false — закрито й вичерпано
Правила: закривати має лише відправник, і лише раз. Надсилання в закритий канал панікує; закриття вже закритого каналу панікує. Закриття — це широкомовлення: його бачить кожен отримувач.
Перебір каналу через range¶
for v := range ch отримує значення, доки канал не буде закрито й
вичерпано, а тоді завершує цикл. Це чистий спосіб споживати потік:
greetings := make(chan string)
go func() { // виробник
for _, name := range []string{"alice", "bob", "carol"} {
greetings <- "hello, " + name
}
close(greetings) // більше значень немає; без цього range блокується назавжди
}()
for g := range greetings { // споживач — завершується, коли канал закрито
fmt.Println(g)
}
// output:
// hello, alice
// hello, bob
// hello, carol
Напрям каналу в сигнатурах¶
Параметр функції може обмежити канал до лише надсилання (chan<- T) чи
лише отримання (<-chan T). Це документує намір і дозволяє компілятору
зупиняти неправильне використання.
func produce(out chan<- int) { out <- 42; close(out) } // лише надсилання
func consume(in <-chan int) { fmt.Println(<-in) } // лише отримання
ch := make(chan int, 1)
produce(ch)
consume(ch) // output: 42
Взаємні блокування та nil-канали¶
Якщо кожна горутина заблокована в очікуванні каналу, середовище це виявляє й аварійно завершується:
func main() {
ch := make(chan int)
<-ch // ніхто ніколи не надішле
}
// fatal error: all goroutines are asleep - deadlock!
nil-канал (для якого не викликали make) блокується назавжди і на
надсиланні, і на отриманні — інколи корисно в select, щоб вимкнути
випадок, але інакше це баг.
Швидка довідка¶
| Операція | Значення |
|---|---|
make(chan T) |
небуферизований — рандеву надсилання/отримання |
make(chan T, n) |
буферизований — блокує лише коли повний/порожній |
ch <- v / v := <-ch |
надіслати / отримати |
v, ok := <-ch |
ok стає false, коли закрито й вичерпано |
close(ch) |
більше жодних надсилань; лише відправник, раз |
for v := range ch |
отримувати, доки не закрито |
chan<- T / <-chan T |
параметр лише-надсилання / лише-отримання |
| надсилання в закритий канал | паніка |
| усі горутини заблоковані | fatal: deadlock |
Джерела¶
- Channel types — go.dev/ref/spec#Channel_types
- Send statements — go.dev/ref/spec#Send_statements
- Receive operator — go.dev/ref/spec#Receive_operator
- Close — pkg.go.dev/builtin#close
- Effective Go: channels — go.dev/doc/effective_go#channels
- Go blog: share memory by communicating — go.dev/blog/codelab-share