Some golang channel observations

25-Jan-2018

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)

} 

golang playground

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.

© 2017 | Hucore theme & Hugo