tldr;
Working on an interesting golang tutorial using channels I came to a few helpful conclusions.
Starting Out
The particular problem I was solving was a rock-paper-scissors game where I was trying to determine my opponent’s choice and make sure that I would beat them (see this) when we came to the ‘showdown’. There was one channel that was passed to both my ‘player’ function and my ‘opponent’, with which you could send and recieve a struct with a user id along with the choice made: 1-Rock, 2-Paper, and 3-Scissors. There was also an additional channel that was passed to the functions as well - with which an empty struct was sent and recieved.
Right off the bat
Even after reading the golang documentation on channels they seemed a little strange to me. I continued to run into many ‘locking’ errors as I worked through the exercise trying to recieve the Choice struct from the Guess channel. By the way - the error was: fatal error: all goroutines are asleep - deadlock! Searching through the first few posts on Stack Overflow it became clear that any channel created without a buffer length is synchronous - it will block on the main thread until a read is performed. For example:
package main
import (
"fmt"
"time"
)
func main() {
messages := make(chan string)
go func() {
fmt.Println("About to send the message")
messages <- "ping"
}()
time.Sleep(time.Second * 3)
fmt.Println("blocked")
msg := <-messages
fmt.Println(msg)
}
golang playground ref: http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
Timing is everything
Closing a channel unblocks the main thread and allows reading of objects off the channel to continue - where it is closed is important:
package main
import (
"fmt"
)
func main() {
// Data is irrelevant
channelclose := make(chan int)
go func() {
fmt.Println("goroutine message")
channelclose <- 1
// Just send a signal "I'm done"
close(channelclose)
}()
fmt.Println("main function message")
intvar := <-channelclose
fmt.Println("Here's the channel value:",intvar)
}
golang playground ref: http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
Notice that by closing the channel right after an int is sent to the channel the main thread becomes ‘unblocked’ and the subsequent reading from channelclose continues without error. There is no need to set a value when reading from a channel - an empty read can be used to unblock/synchronize the running program:
package main
func main() {
done := make(chan bool)
go func() {
println("goroutine message")
// We are only interested in the fact of sending itself,
// but not in data being sent.
done <- true
}()
println("main function message")
<-done
println("done channel not blocking")
}
golang playground ref: http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
Sometimes sending nothing is the best course of action
An interesting point about the last code snippet above is that an empty read can unblock the main thread and actually help especially when using mutilple channels and accessing objects while sending and reading. For the rock-paper-scissors problem an empty struct was sent and read in a second channel to allow operations on objects and other channels to complete in a more synchronous manner (there’s a sync package in golang that can be used for synchronization but that’s to come in a later blog post ;)):
package main
import (
"fmt"
)
func emptystruct(mainchannel chan bool, emptystructchan chan struct{}) {
<-emptystructchan
for i := 0; i < 3; i++ {
fmt.Println("One and a two and a:", i)
mainchannel <- true
}
emptystructchan <- struct{}{}
}
func main() {
mainchannel := make(chan bool)
emptystructchan := make(chan struct{})
go emptystruct(mainchannel,emptystructchan)
emptystructchan <- struct{}{}
<- mainchannel
<- mainchannel
<- mainchannel
fmt.Println("All reads from mainchannel completed")
close(emptystructchan)
}
There’s alot more to channels than what’s been sampled here - it took me almost 3 days to get the rock-paper-scissors problem worked out after scouring google stem to stern - but they are certainly a powerful type in golang and are useful in setting up pipelines and concurrent flows. More to come on channels later, including stumbling through the use of the sync package….
Thanks for reading.