Channels
Channels are a duck typing feature.
Description
Channels are a pipeline. Their primary use case is passing values between two goroutines.
Channels must be initialized like:
ch := make(chan string)
By default a channel is unbuffered, meaning that it halts on 'sends' until a value can be 'received'. To instead initialize a channel with a buffer that can queue up to N values before halting, try:
ch := make(chan string, 2)
Values are sent and received like:
func main() {
ch := make(chan string)
ch <- "foo"
fmt.Println(<-ch)
}
Function Signatures
Function signatures can distinguish between a channel that can be sent on and a channel that can be received on.
func i_only_send(ch chan<- string) {
ch <- "foo"
}
func i_only_receive(ch <-chan string) {
fmt.Println(<-ch)
}
func i_can_do_anything(ch chan string) {
ch <- "foo"
fmt.Println(<-ch)
}
Synchronization
A channel can be used in a purposefully blocking manner to force synchronization across goroutines.
func worker(done chan struct{}) {
time.Sleep(3 * time.Second)
done <- struct{}{}
}
func main() {
done := make(chan struct{}, 1)
go worker(done)
<-done //`main` waits until `worker` sends a value through `done`
}
Closing
Aside from sending a value, a close event can also be sent.
func sending_something(ch chan<- string) {
ch <- "foo"
time.Sleep(3 * time.Second)
close(ch)
}There are two ways to read the close event on the receiving side. First, by receiving the optional second value, as in:
_, more := <-ch
if !more {
fmt.Println("no more!")
}This can be used like:
func doing_something(ch <-chan string) {
s, ok := <-ch
if ok {
fmt.Println(s)
s, ok = <-ch
}
}Second, when iterating over the channel, a close event ends the loop.
func doing_something(ch <-chan string) {
for s := range ch {
fmt.Println(s)
}
}A close event can be deferred like:
defer close(ch)
A receiver should never close a channel. If a design like this is necessary, use two channels instead.
func doing_something(rx <-chan string, tx chan<- struct{}) {
for s := range rx {
if s == "magic phrase" {
tx <- struct{}{}
return
}
fmt.Println(s)
}
}
Switching
A receiver can switch between channels based on which is ready. If multiple are ready, one is picked randomly.
func summation(ch chan int, quit chan struct{}) {
sum := 0
for {
select {
case i <-ch:
sum += i
case <-quit:
fmt.Println(sum)
default:
time.Sleep(3 * time.Second)
}
}
}