Interesting ways of using Go channels

I’ve created this post to document slides accompanying a talk on Go channels given by John Graham-Cumming during GopherCon 2014. The presentation was entitled ‘A Channel Compendium‘ and is available below.

During the talk, he presents interesting ways of using Go channels and makes you aware of the possibilities and advantages of concurrent programming. For me personally, this opened my eyes to several new ways of structuring programs and novel techniques for synchronising work done across multiple processor cores.

The following examples demonstrate various techniques how to use channels in Go. The code has been intentionally simplified to express these rather than being production level. For example, all error handling has been omitted.

Signalling

Wait for an event

In this example, a goroutine is started, does some work (in this case waiting for five seconds) then closes the channel. Unbuffered channels always halt the current goroutine until communication can take place. Closing the channel signals to the goroutine that it can continue because there is no more data to be received. Closed channels never halt execution of the goroutine.

Coordinate multiple goroutines

In this example, a hundred goroutines are started, waiting for communication of data on the start channel (or for it to be closed). In this case, once closed, all goroutines start.

Coordinated termination of workers

In this example, a hundred goroutines are started, waiting for communication of data on the die channel (or for it to be closed). In this case, once closed all goroutines end.

Verify termination of workers

In this example, a goroutine is started, waiting for communication of data on the die channel (or for it to be closed). In this case, once closed, the goroutine performs termination tasks, then signals to the main goroutine (via the same die channel) that it’s finished.

Hide state

Unique ID service

In this example, a goroutine is started to generate unique hexadecimal id’s. Each id is sent via the id channel and the goroutine halts until the channel is read. Each time the channel is read, the goroutine is free to increment the value and send another.

Memory recycler

In this example, a goroutine is started to recycle memory buffers. The give channel receives old memory buffers and stores them in a list. While the get channel dispenses these buffers for use. If no buffers are available in the list, a new one is created.

Capped memory recycler

In this example, a single buffered channel is used as a store for memory buffers. The channel is set to buffer five entries at any given time. This means the channel will not halt the current goroutine if there is capacity in the channel to accept another entry.

The select statements provide non-blocking access to this channel in the case that it would be full. The first select creates a new buffer if it’s unable to get a buffer from the store. The second select defaults to nothing if it’s unable to place one on the store, which invokes the garbage collector to deallocate the given buffer.

Nil channels

Disable receiving case statements

In this example, a goroutine is started and using select tries to receive on two channels. If a channel is closed, it is set to nil. Because nil channels always block, this has the effect of disabling the associated case statement. If both channels are set to nil, the goroutine exits because it cannot receive anything else.

Disable sending case statements

In this example, a goroutine is started to generate random numbers and send them on the c channel. If a message is sent on the d channel, the c channel is set to nil, disabling the associated case statement. Once disabled, the goroutine can no longer send random numbers.

Timers

Timeout

In this example, a goroutine is started to do some work. A timeout channel is created to make sure a case is executed if the select is halting for too long. In this case, the goroutine is terminated after thirty seconds of being idle. The timeout is assigned on every iteration of the select to make sure that if work is done, the timeout is reset.

Heartbeat

In this example, a goroutine is started to do some work. A heartbeat channel is created to make sure a case statement is executed at regular intervals. The heartbeat channel is not reset on each iteration to make sure it always executes on time.

Examples

Network multiplexer

This example demonstrates a simple network multiplexer. Within the main goroutine, a channel is created to handle transferring messages and a network connection is established. A hundred goroutines are then spawned to generate strings (to act as our messages) and sent along this channel. Each message is read from the channel during a continuous loop and sent to the network connection.

This example doesn’t run (because we are trying to connect to an example domain) but it expresses how easy it is to have many asynchronous processes sending messages to a single network connection.

First response

In this example, an array of web URL’s are iterated upon and passed individually to separate goroutines. Each goroutine executes asynchronously and queries the passed URL. Each query response is passed into the first channel, which (of course) ensures the first query to respond is the first passed into the channel. We can then read this response from the channel and act accordingly.

Passing a channel

In this example, the w channel is created to transfer a unit of work to a goroutine. This unit of work is received and a request is made to the contained URL. As part of this work, a resp channel is also passed. Once the request is performed, the response is sent back along the response channel. This allows this goroutine to process work and send a response back on different channels configured for each unit of work.

HTTP load balancer

In this example, a load balancer has been created building upon previous examples. It handles reading URL’s from stdin and starting goroutines to perform a request for each. Each request is passed through a load balancer to filter these jobs into a finite number of workers. These workers process the requests and ultimately return a response to a single channel.

Using a load balancer such as this can take a huge number of requests, balance them across resources available and process them in an orderly manner.

Conclusion

Go is a language that in my opinion does have it’s problems but it’s a language I’m willing to learn and use. For me, this presentation of ideas opened my mind to new concepts and really gets me interested in starting a new project which takes advantage of the fantastic concurrency support in Go. It also reinforces the need to read and understand the standard library that ships with languages such as Go to truly understand the ethos and design decisions of the language.