Go Channels

Since the first thing I liked about Go is channels, I’ll talk some more about them.

IA channel can transmit a value of a specific type. That is, a channel value of type chan T can transmit any value of type T. This is not much of a restriction, as the type interface {} can be used to match any value at all.

There are two different kinds of channels: buffered and unbuffered. Unbuffered ones are the default. An unbuffered channel provides synchronous transmission of a value from a sender to a receiver. This means that if you try to send a value on the channel, and nothing is receiving from the channel, then you will block until there is a receiver. Similarly, if you try to receive and nothing is sending, you will block until there is a sender (which is pretty obvious). In other words, an unbuffered channel serves as a synchronization point between two goroutines. This is the key point of the Go memory model, which more or less amounts to saying that if you receive a value on a channel, then you will see all the writes made by the sender of the channel.

Buffered channels are more straightforward: if you write a value to the channel, it simply goes into the buffer. If there isn’t room, you want until there is room.

Implementing the above is fairly straightforward. However, there is another aspect to channels in Go: the select statement. The select statement permits a program to wait until some operation on a channel can proceed. It can list an arbitrary number of channels to wait for, and for each channel it can indicate whether it wants to wait to send a value or to receive a value. Implementing this efficiently is at the heart of the goroutine scheduling mechanism. For example, one interesting case is when one goroutine is selecting to write to an unbuffered channel while another goroutine is selecting to read from the channel. The scheduler must get both goroutines to synch up and transmit the value.

In gccgo I currently use a thread for each goroutine, although this will have to change at some point. In order to implement select, each unbuffered channel has a linked list of select statements that are waiting for the channel to be available. When a new select statement comes along such that a send and a receive are linked, and the new select statements picks that operation to run, it has to wake up the other select such that it is forced to use the new channel. This can fail due to race conditions if there is a third select statement involved, in which case the first select has to restart. This was all a huge headache to get working, and I completely rewrote all the select code three times.






Leave a Reply