Szukaj…
Wprowadzenie
Kanał zawiera wartości danego typu. Wartości mogą być zapisywane na kanale i odczytywane z niego, i krążą w kanale w kolejności „pierwsze w pierwsze”. Istnieje rozróżnienie między buforowanymi kanałami, które mogą zawierać kilka komunikatów, a niebuforowanymi kanałami, które nie mogą. Kanały są zwykle używane do komunikacji między goroutynami, ale są również przydatne w innych okolicznościach.
Składnia
- make (chan int) // utwórz niebuforowany kanał
- make (chan int, 5) // utwórz buforowany kanał o pojemności 5
- close (ch) // zamyka kanał „ch”
- ch <- 1 // zapisz wartość 1 na kanale „ch”
- val: = <-ch // odczyt wartości z kanału „ch”
- val, ok: = <-ch // alternatywna składnia; ok to bool wskazujący, czy kanał jest zamknięty
Uwagi
Kanał z pustą strukturą make(chan struct{})
jest jasnym komunikatem dla użytkownika, że żadna informacja nie jest przesyłana kanałem i że jest on wykorzystywany wyłącznie do synchronizacji.
W odniesieniu do niebuforowanych kanałów, zapis kanału będzie blokowany, dopóki odpowiedni odczyt nie nastąpi z innego goroutine. To samo dotyczy blokowania odczytu kanału podczas oczekiwania na pisarz.
Korzystanie z zasięgu
Podczas odczytywania wielu wartości z kanału używanie range
jest powszechnym wzorcem:
func foo() chan int {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
ch <- 3
close(ch)
}()
return ch
}
func main() {
for n := range foo() {
fmt.Println(n)
}
fmt.Println("channel is now closed")
}
Wynik
1
2
3
channel is now closed
Limit czasu
Kanały są często używane do wdrażania limitów czasu.
func main() {
// Create a buffered channel to prevent a goroutine leak. The buffer
// ensures that the goroutine below can eventually terminate, even if
// the timeout is met. Without the buffer, the send on the channel
// blocks forever, waiting for a read that will never happen, and the
// goroutine is leaked.
ch := make(chan struct{}, 1)
go func() {
time.Sleep(10 * time.Second)
ch <- struct{}{}
}()
select {
case <-ch:
// Work completed before timeout.
case <-time.After(1 * time.Second):
// Work was not completed after 1 second.
}
}
Koordynujące goroutine
Wyobraź sobie goroutine z dwuetapowym procesem, w którym główny wątek musi wykonać trochę pracy między każdym krokiem:
func main() {
ch := make(chan struct{})
go func() {
// Wait for main thread's signal to begin step one
<-ch
// Perform work
time.Sleep(1 * time.Second)
// Signal to main thread that step one has completed
ch <- struct{}{}
// Wait for main thread's signal to begin step two
<-ch
// Perform work
time.Sleep(1 * time.Second)
// Signal to main thread that work has completed
ch <- struct{}{}
}()
// Notify goroutine that step one can begin
ch <- struct{}{}
// Wait for notification from goroutine that step one has completed
<-ch
// Perform some work before we notify
// the goroutine that step two can begin
time.Sleep(1 * time.Second)
// Notify goroutine that step two can begin
ch <- struct{}{}
// Wait for notification from goroutine that step two has completed
<-ch
}
Buforowane a niebuforowane
func bufferedUnbufferedExample(buffered bool) {
// We'll declare the channel, and we'll make it buffered or
// unbuffered depending on the parameter `buffered` passed
// to this function.
var ch chan int
if buffered {
ch = make(chan int, 3)
} else {
ch = make(chan int)
}
// We'll start a goroutine, which will emulate a webserver
// receiving tasks to do every 25ms.
go func() {
for i := 0; i < 7; i++ {
// If the channel is buffered, then while there's an empty
// "slot" in the channel, sending to it will not be a
// blocking operation. If the channel is full, however, we'll
// have to wait until a "slot" frees up.
// If the channel is unbuffered, sending will block until
// there's a receiver ready to take the value. This is great
// for goroutine synchronization, not so much for queueing
// tasks for instance in a webserver, as the request will
// hang until the worker is ready to take our task.
fmt.Println(">", "Sending", i, "...")
ch <- i
fmt.Println(">", i, "sent!")
time.Sleep(25 * time.Millisecond)
}
// We'll close the channel, so that the range over channel
// below can terminate.
close(ch)
}()
for i := range ch {
// For each task sent on the channel, we would perform some
// task. In this case, we will assume the job is to
// "sleep 100ms".
fmt.Println("<", i, "received, performing 100ms job")
time.Sleep(100 * time.Millisecond)
fmt.Println("<", i, "job done")
}
}
Blokowanie i odblokowywanie kanałów
Domyślnie komunikacja za pośrednictwem kanałów jest synchronizowana; kiedy wysyłasz jakąś wartość, musi być odbiornik. W przeciwnym razie pojawi się fatal error: all goroutines are asleep - deadlock!
następująco:
package main
import "fmt"
func main() {
msg := make(chan string)
msg <- "Hey There"
go func() {
fmt.Println(<-msg)
}()
}
Bu istnieje rozwiązanie: użyj buforowanych kanałów:
package main
import "fmt"
import "time"
func main() {
msg :=make(chan string, 1)
msg <- "Hey There!"
go func() {
fmt.Println(<-msg)
}()
time.Sleep(time.Second * 1)
}
Oczekiwanie na zakończenie pracy
Częstą techniką korzystania z kanałów jest tworzenie pewnej liczby pracowników (lub konsumentów) do czytania z kanału. Korzystanie z synchronizacji.WaitGroup to łatwy sposób oczekiwania na zakończenie pracy przez pracowników.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
numPiecesOfWork := 20
numWorkers := 5
workCh := make(chan int)
wg := &sync.WaitGroup{}
// Start workers
wg.Add(numWorkers)
for i := 0; i < numWorkers; i++ {
go worker(workCh, wg)
}
// Send work
for i := 0; i < numPiecesOfWork; i++ {
work := i % 10 // invent some work
workCh <- work
}
// Tell workers that no more work is coming
close(workCh)
// Wait for workers to finish
wg.Wait()
fmt.Println("done")
}
func worker(workCh <-chan int, wg *sync.WaitGroup) {
defer wg.Done() // will call wg.Done() right before returning
for work := range workCh { // will wait for work until workCh is closed
doWork(work)
}
}
func doWork(work int) {
time.Sleep(time.Duration(work) * time.Millisecond)
fmt.Println("slept for", work, "milliseconds")
}