Intro
A go maxim or proverb is:
Do not communicate by sharing memory; instead, share memory by communicating.
So what is a channel?
A channel is a “typed” conduit (pipes) mechanism for goroutines to synchronize execution and communicate by passing values, using channel operator, <-. They are mechanism for communication between goroutines.
Syntax
// can only be used to send float64s
chan<- float64
// can only be used to receive ints
<-chan int
// can be used to send and receive values of type Dosa
chan Dosa
(The data flows in the direction of the arrow.)
Like maps and slices, channels must be created before use:
ic := make(chan int) // unbuffered channels
dc := make(chan *Dosa, 10) // buffered channel
To send a value on a channel, use <- as a binary operator.
To receive a value on a channel, use it as a unary operator.
ic <- 3 // Send 3 on the channel.
dosa := <-dc // Receive a pointer to Dosa from the channel.
Buffered and unbuffered(normal) channels
Unbuffered/Normal channels
- If the capacity of a channel is zero or absent, the channel is unbuffered and the sender blocks until the receiver has received the value.
Normal ChannelsareSynchronous. i.e. Both the sending side and the receiving side of thechannelwait until the other side is ready.
Buffered channel
If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.
Buffered ChannelsareAsynchronous. i.e. Sending and Receiving messages throughBuffered Channelswill not block unless theChannelis full.We can create a
Buffered Channelsame way as we create theNormal Channelsusing the built-inmake()function. The only difference is, we can pass the second parameter tomake()function which indicates the Buffered Channel’sBuffering Capacity.For e.g.
ch := make(chan type, capacity)NOTE: If we pass the
Buffering Capacityas1, we are creating aNormal Channel. To create aBuffered Channel, we have to passBuffering Capacityasgreater than 1Receivers always block until there is data to receive.
Sending or receiving from a
nilchannel blocks forever.
Closing a channel
The close function records that no more values will be sent on a channel. (Sending to or closing a closed channel causes a run-time panic. Closing a nil channel also causes a run-time panic.)
Note: Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.
Another note: Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop. (We will cover range loop for buffered channels soon.)
After calling close, and after any previously sent values have been received, receive operations will return a zero value without blocking. A multi-valued receive operation additionally returns an indication of whether the channel is closed.
ch := make(chan string)
go func() {
ch <- "Hello!"
close(ch)
}()
fmt.Println(<-ch) // Print "Hello!".
fmt.Println(<-ch) // Print the zero value "" without blocking.
fmt.Println(<-ch) // Once again print "".
v, ok := <-ch // v is "", ok is false.
// Receive values from ch until closed.
for v := range ch {
fmt.Println(v) // Will not be executed.
}
Note that it is only necessary to close a channel if a receiver is looking for a close.
Example 1 - Unbuffered channel
func sayNamasteFromChannel(msg chan string, msgType int) {
if msgType == 1 {
msg <- "नमस्ते किंशुक!"
} else {
msg <- "नमस्ते रेखा!"
}
}
func main() {
msgFromChannel := make(chan string)
fmt.Printf("msgFromChannel: %#v\n", msgFromChannel)
go sayNamasteFromChannel(msgFromChannel, 1)
fmt.Println("Message from Channel:", <-msgFromChannel)
go sayNamasteFromChannel(msgFromChannel, 2)
fmt.Println("Message from Channel:", <-msgFromChannel)
fmt.Println("Inside main() function")
close(msgFromChannel)
}
Output:
msgFromChannel: (chan string)(0xc0000220c0)
Message from Channel: नमस्ते किंशुक!
Message from Channel: नमस्ते रेखा!
Inside main() function
Example 2 - Unbuffered channel with block
var done chan bool = make(chan bool)
func sayNamaste(source string) {
for i := 0; i < 9; i++ {
fmt.Println("नमस्ते किंशुक!", i, source)
}
if source == "Goroutine" {
done <- true
}
}
func main() {
go sayNamaste("Goroutine")
sayNamaste("main() Goroutine")
<-done // block until done has value
}
Output:
नमस्ते किंशुक! 0 main() Goroutine
नमस्ते किंशुक! 1 main() Goroutine
नमस्ते किंशुक! 2 main() Goroutine
नमस्ते किंशुक! 3 main() Goroutine
नमस्ते किंशुक! 4 main() Goroutine
नमस्ते किंशुक! 5 main() Goroutine
नमस्ते किंशुक! 6 main() Goroutine
नमस्ते किंशुक! 7 main() Goroutine
नमस्ते किंशुक! 8 main() Goroutine
नमस्ते किंशुक! 0 Goroutine
नमस्ते किंशुक! 1 Goroutine
नमस्ते किंशुक! 2 Goroutine
नमस्ते किंशुक! 3 Goroutine
नमस्ते किंशुक! 4 Goroutine
नमस्ते किंशुक! 5 Goroutine
नमस्ते किंशुक! 6 Goroutine
नमस्ते किंशुक! 7 Goroutine
नमस्ते किंशुक! 8 Goroutine
Example - Cause deadlock
var done chan bool = make(chan bool)
func sayNamaste(source string) {
for i := 0; i < 9; i++ {
fmt.Println("नमस्ते किंशुक!", i, source)
}
if source == "Goroutine" {
done <- true
}
}
func main() {
go sayNamaste("Another Goroutine") // will not allow setting done to true, Remove "Another " to fix the deadlock
sayNamaste("main() Goroutine")
<-done // block until done has value
}
Output:
नमस्ते किंशुक! 0 main() Goroutine
नमस्ते किंशुक! 1 main() Goroutine
नमस्ते किंशुक! 2 main() Goroutine
नमस्ते किंशुक! 3 main() Goroutine
नमस्ते किंशुक! 4 main() Goroutine
नमस्ते किंशुक! 5 main() Goroutine
नमस्ते किंशुक! 6 main() Goroutine
नमस्ते किंशुक! 7 main() Goroutine
नमस्ते किंशुक! 8 main() Goroutine
नमस्ते किंशुक! 0 Another Goroutine
नमस्ते किंशुक! 1 Another Goroutine
नमस्ते किंशुक! 2 Another Goroutine
नमस्ते किंशुक! 3 Another Goroutine
नमस्ते किंशुक! 4 Another Goroutine
नमस्ते किंशुक! 5 Another Goroutine
नमस्ते किंशुक! 6 Another Goroutine
नमस्ते किंशुक! 7 Another Goroutine
नमस्ते किंशुक! 8 Another Goroutine
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/Users/kinshuk/lyf/scm/github/k2/golang-examples/8.concurrency/2-channels/3-unbuffered-channel-deadlock/main.go:21 +0x7e
exit status 2
Example - Publish news
In the following example we let the Publish function return a channel, which is used to broadcast a message when the text has been published.
// Publish prints text to stdout after the given time has expired.
// It closes the wait channel when the text has been published.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
ch := make(chan struct{})
go func() {
time.Sleep(delay)
fmt.Println(text)
close(ch)
}()
return ch
}
Notice that we use a channel of empty structs to indicate that the channel will only be used for signalling, not for passing data.
This is how you might use this function.
func main() {
wait := Publish("A new goroutine has started.", 3*time.Second)
fmt.Println("Let’s hope that another Goroutine will publish before main finishes.")
<-wait // Block until the text has been published.
fmt.Println("Main Goroutine Update: Published the news, calling it done.")
}
Output:
Let’s hope that another Goroutine will publish before main finishes.
Another coroutine update: A new goroutine has started.
Main Goroutine Update: Published the news, calling it done.
Deadlock
If you remove the call to close from the example above, the goroutine started by the Publish function will print the news and leave the code in the other goroutine waiting forever. This condition is known as a deadlock.
A deadlock is a situation in which goroutines are waiting for each other and none of them is able to proceed.
Go has a mechanism for detecting deadlocks. We will discuss that later.
By default, sends and receives block wait until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
Example - Buffered channels
Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel:
ch := make(chan int, 100)
Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.
func main() {
// Created buffered channel of strings with a capacity of 3
// This means a Channel Buffer can hold up to 3 values
msgQueue := make(chan string, 3)
msgQueue <- "One"
msgQueue <- "Two"
msgQueue <- "Three"
// We drain the msgQueue by receiving all the values from the buffered channel
fmt.Println(<-msgQueue)
fmt.Println(<-msgQueue)
fmt.Println(<-msgQueue)
}
Output:
One
Two
Three
Ranging over channels
A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression: after
v, ok := <-ch
ok is false if there are no more values to receive and the channel is closed.
The loop for i := range ch receives values from the channel repeatedly until it is closed.
Example - Ranging over channels
func main() {
// Created buffered channel of strings with a capacity of 3
// This means a Channel Buffer can hold up to 3 values
msgQueue := make(chan string, 3)
msgQueue <- "One"
msgQueue <- "Two"
msgQueue <- "Three"
// Even though we close this non-empty Channel, we can still receive
// the remaining values (see below)
close(msgQueue)
// We use the range keyword to iterate over each element as it gets
// received from the msgQueue
for m := range msgQueue {
fmt.Println(m)
}
}
Output:
One
Two
Three
http://www.golangbootcamp.com/book/concurrency#uid131