-
Notifications
You must be signed in to change notification settings - Fork 3
Go Concurrency Channels
Go-routine is a concurrently executing activity. Golang supports two styles of concurrent programming:
- CSP (Communicating Sequential Process) concurrency model, in which values are passed between between go-routines (using channels) but variables are confined to single activity.
- Shared Memory Multithreading: in which shared memories is used using mutex primitives.
Go uses, Channels as communication mechanisms used to pass data among go-routines. A channel is created as:
ch := make(chan int) // create a synchronous channel to pass ints
type ints chan<-int
integers := make(chan ints) // create a synchronous channel, of int channels
Like maps
a channel
is a reference type. A zero value is nil. Two channels of same type are comparable using ==
, true
if both reference same underlying data structure.
Channels support three operations:
-
send
ch <- value
sends a value to channel, ch -
receive
value = <- ch
receives a value from channel, ch -
close(ch)
, closes a channel:
- a send on closed channel will cause panic and
- a receive on closed channel will fetch unread data and then immediately return zero-value if empty
Create unbuffered channel by using 0 in capacity argument of make. A Send operation on a unbuffered channel blocks the sending go-routine until data is read from the channel, Playground example.
ch := make(chan int) // create unbuffered int channel
wait := make(chan struct{}, 0) // or, create unbuffered message channel
go func () {
ch <- 1
ch <- 2 // Blocks until, 1 ch is read from ch. Blocks forever
wait <- struct{}{}
}()
<- ch // read 1
<- ch // wait until 2 is written
<- wait // for anonymous go-routine to finish. Used to explain concept
Buffered channels can be used to:
- Synchronize go-routines.
- Create pipeline among go-routines
- Create unidirectional data paths e.g input channel (mnemonic: <- chan) or output channel (mnemonic: chan<-).
Example of unidirectional channel:
// printers argument is a read only "in" channel. It can not write "in" channel.
func printers (in <-chan int) {}
// squares writes squares in "out" channel of corresponding ints "in" channel. It can not read "out" channel
func squares (out chan<- int, in <-chan int) {}
It is not necessary to close a channel, as channels not closed are reclaimed by garbage collector. It is only necessary to close a channel when sender wants receiver to know that all data has been sent. There are three ways for receiver to know channel is closed:
- Use
range
to read the values - Use pattern
v, ok := <- ch
if ok isfalse
channel is closed. - Returns zero-value immediately.
See example of closing a channel and receiver determining if a channel is closed using 2 and 3 above.
Similar to maps, channel return ok as true if value is read from channel. And false, it is read from closed channel.
readInts:
for {
select {
case x, ok := <-ints:
if ok == true {
fmt.Printf("%d ", x)
} else {
fmt.Printf("\n2. readInts: Channel is closed.")
break readInts // channel closed, break out of `readInts:` block
}
}
}
// isClosed checks if a channel is closed
func isClosed(ch chan int) bool {
select {
case <-ch:
return true
default:
return false
}
}