Sunday, July 15, 2012

Go channels in good old C

Last week I wrote about goroutines and channels, and said that you could do the same thing in C by using pthreads and pipes. That's not exactly true. Although a pipe is well-suited for sending data from one thread to another, like in a parent-and-child-process situation, it's not going to work with multiple readers and writers. Well, how about a socketpair? No (!), same problem here. To implement something like a Go channel in C, you have to put in some extra effort.

The Go channel is a queue of items. Multiple threads may access the queue, and it is guaranteed that the concurrent access is safe and free of race conditions. In C you would implement such a queue as an array and include a mutex lock for doing mutual exclusion.
typedef struct {
    size_t max_items, num_items, item_size;
    void *items;
    pthread_mutex_t mutex;
    pthread_cond_t cv;
} chan_t;

chan_t *make_chan(size_t item_size, size_t max_items);
void read_chan(chan_t *c, void *restrict item);
void write_chan(chan_t *c, const void *item);
void close_chan(chan_t *c);
A channel can be initialized to hold any type. In standard C, "any type" means using a void pointer. Mind that because of this, the interface is not fully type-safe. It is up to the programmer to use the channel in the correct way. In C++ you would use a template and have type-safe code.

The items pointer points at an array of items for a buffered channel. For an unbuffered channel, set max_items to one. The mutex and the condition variable work together to take care of the locking. Reading from the channel will wait on the condition, while a write to the channel will signal the condition. Since C does not do automatic garbage collection, close_chan() will deallocate the channel. Certainly, close_chan() should be called in one thread only.

The full code is only like a hundred lines. Not too big, but too big to include here. With this code you can have threads easily communicate just like goroutines communicate over channels in Go. Having channels in C is nice. The main program code, the code that really matters, is so much easier to understand with channels as there are no mutex locks cluttering the code anymore. Now it's also possible to use Go idioms like waiting for all threads to finish using a channel (by writing a value of true to a chan bool named done).

You can write anything you like in C. Still, channels in Go are more powerful than what I presented here. In Go, you can select on channels. Mimicking Go's select in C is not easy ... (grinds teeth). But why bother with C anyway...? It's time to Go.