Go Channel
Channels are a typed, thread-safe queue. Channels allow different goroutines to communicate with each other. 1
Channels send and receive values with the channel operator:
// the data flows in the direction of the arrow.
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
Channels must be created before use. They use the make
keyword:
ch := make(chan int)
By default, sends and receives block (which means your code safely waits) until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
Deadlock
A deadlock is when a group of goroutines are blocking, so none of them can continue. This is a common problem in concurrent programming.
// a send to a nil channel blocks forever
func () {
var c chan string
c <- "let's get started" // deadlock
}()
// a receive from a nil channel blocks forever
func () {
var c chan string
fmt.Println(<-c) // deadlock
}()
- See more:
Channels Continued
Notice that we can block and wait until something is sent on a channel using the following syntax.
// this will block until it pops a single item off the channel, then continue, discarding the item.
<-ch
package main
import (
"fmt"
"time"
)
func main() {
ch := getDatabasesChannel()
<-ch
<-ch
<-ch
<-ch
time.Sleep(1 * time.Second)
fmt.Println("mailio server ready")
fmt.Println("vim-go")
}
func getDatabasesChannel() chan struct{} {
ch := make(chan struct{})
go func() {
ch <- struct{}{}
fmt.Println("first db online")
ch <- struct{}{}
fmt.Println("second db online")
ch <- struct{}{}
fmt.Println("third db online")
ch <- struct{}{}
fmt.Println("fourth db online")
}()
return ch
}
/*
OUTPUT
first db online
second db online
third db online
fourth db online
mailio server ready
vim-go
*/
Buffered Channels
Channels can optionally be buffered. You may provide a buffer length as the second argument to make()
to create a buffered channel:
// sending on a buffered channel only blocks when the buffer is full.
ch := make(chan int, 100)
package main
import (
"fmt"
)
func main() {
emailsToSend := make(chan string, 2)
emailsToSend <- "Hello John, tell Kathy I said hi"
emailsToSend <- "Whazzup bruther"
fmt.Println(<-emailsToSend)
fmt.Println(<-emailsToSend)
}
/*
OUTPUT
Hello John, tell Kathy I said hi
Whazzup bruther
*/
Closing channels in Go
close(ch)
// receivers can check the ok value when receiving from a channel to test if a channel was closed.
v, ok := <-ch
ok
is false
if the channel is empty and closed.
Sending on a closed channel will cause a panic.
Range
Similar to slices and maps, channels can be ranged over.
// the below will receive values over the channel (blocking at each iteration if nothing new is there) and will exit only when the channel is closed.
for item := range ch{
}
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 10)
go fibonacci(cap(ch), ch)
for num := range ch {
fmt.Println(num)
}
}
func fibonacci(n int, ch chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
ch <- x
x, y = y, x+y
}
close(ch)
}
/*
OUTPUT
0
1
1
2
3
5
8
13
21
34
*/
Select
Sometimes we have a goroutine listening to multiple channels and want to process data in the order it comes through, no matter which channel sends the data first.
select {
case i, ok := <- chInts:
fmt.Println(i)
case s, ok := <- chStrings:
fmt.Println(s)
}
// Select Default Case
select {
case i := <-cH:
// use i
default:
// receiving from ch would block
}
time.Ticker
time.Tick()
is a standard library function that returns a channel that sends a value on a given interval.time.After()
sends a value once after the duration has passed.time.Sleep()
blocks the current goroutine for the specified amount of time.
package main
import (
"fmt"
"time"
)
func main() {
snapshotInterval := time.Tick(800 * time.Millisecond)
saveAfter := time.After(2800 * time.Millisecond)
for {
select {
case <-snapshotInterval:
fmt.Println("taking backup snapshot...")
case <-saveAfter:
fmt.Println("all backups saved!")
return
default:
fmt.Println("nothing to do, waiting...")
time.Sleep(500 * time.Millisecond)
}
}
}
/*
OUTPUT
nothing to do, waiting...
nothing to do, waiting...
taking backup snapshot...
nothing to do, waiting...
nothing to do, waiting...
taking backup snapshot...
nothing to do, waiting...
taking backup snapshot...
nothing to do, waiting...
all backups saved!
*/