Concurrency is Hard

Part of Read before contributing, an opinionated guide by William Reade

Concurrency Is Hard

Seriously, there are so many ways to screw up a simple goroutine with a trivial select loop. Gentle misuse of channels can block forever, or panic; data races can inject subtly corrupt data where you’d never expect it.

Luckily, there’s a Go Proverb to fit the situation: “don’t communicate by sharing memory, share memory by communicating”. I think this is terribly insightful in a way that few explanations really bring home, and I’m not sure I’ll do any better, but it’s worth a try:

If you even think about sharing memory, you’re in the wrong paradigm. Channels excel at transferring responsibility: you should consider whatever you put in a channel to be gone, and whatever you received to be yours. If you want to be literal, yes, memory is being shared; but that can happen safely as a side-effect of the robust communication via channel. Focus on getting the communication right, and you get safe memory-sharing for free.

Locks etc embody the opposite approach – two components each have direct access to the same memory at the same time, and they have to directly manage the synchronisation and be mindful of lock-ordering concerns, and it’s really easy to screw up. Responsibility transfer via channel is still quite screwable, don’t get me wrong, but it’s much easier to detect mistakes locally: so ideally you always own all the data in scope, but only until you hand it on to someone else, and it’s relatively easy to check that invariant by inspection (for example, vars not zeroed after they’re sent are suspect).

Of course, sometimes you’ll need to use one of the package sync constructs, but… probably not as a first resort, please. And talk to someone about it first – it’s certainly possible that you’re in a situation where you really do want to synchronise rather than orchestrate, and where a judiciously deployed sync type can significantly simplify convoluted channel usage, but it’s rare. (And be sure you really are simplifying: if you’re not careful, the interactions between locks and channels can be… entertaining.)