When to use or not use channels with goroutines

arjun dhar
4 min readMay 5, 2020

--

Channels → in Go are synonymous with go { routine(s) }

However this unfortunately has some developers believing that, whenever you use a goroutine, you must use channels.

This short blog attempts to diagrammatically explain when one should and should not use channels in go.

For the sake of comparisons to other languages that rely on the term thread rather than what we call goroutine, we will refer to them as Activity, for the sake of neutrality.

When to use channels

There are plenty of articles that already support the use of channels and how they are used, so we wont get into that. But lets start the famous go quote

Do not communicate by sharing memory; instead, share memory by communicating.

So far, so good. But why is this preached?

synch {

We need to understand that shared memory, is difficult to manage in concurrent models. The onus on protecting (or synchronizing) memory access between competing activities, lies on the developer. Ideally if you can get away by using a Mutex, that is great.

In languages like Java, if you find yourself dealing with high level synchronization of large blocks of code or function, or multiple synchronized blocks inter dependent on each other then you are in trouble!

}

Goroutine

A goroutine, can be achieved by putting the go keyword in-front of an ordinary function; sayf(). By design, your function should look the same irrespective of it being called as a go routine or not. This is because in most cases you are providing concurrency to the function. This implies the function itself is running in a concurrent context provided by the goroutine.

When developers write goroutines, they forget that a it is nothing but a function. Coupling a function always to a channel is a prevalent and negative practice. The reason I call it negative, is because how a function can be called with our without a concurrent context is a secondary concern only influenced by what it wishes to communicate with another function. In other words, channels imply coupling between functions, and hence increases design constraints. This complexity must be worth the cost.

When not to use channels

There are two cases of memory sharing that are are thread safe.

Immutable type

go makes no guarantee about immutability; it can be implemented but here we are referring to the idea that state cannot (should not), be changed. It also explains this in the light of other languages where immutability is supported with concurrency in mind.

Immutable types (across languages and not just specific to go), are inherently thread safe. Since they cannot be written to and are intended only for read purposes, they afford to bravely sit in a shared space and be passed around without worrying about the effects of concurrency on them.

Disclaimer: If your immutable is part of a mutable type, then this does not hold true, as there is still an aspect of mutability.

Private items on a stack

Above each activity maintains its own stack of `[x1, x2, x3]`. There is no requirement to share or pass these, and each stack maintains its own copy as it calls functions. It would further help if x was not mutable.

Asynchronous event handlers

Some developers loosely use channels, for even situations where concurrency is not needed; where one can use event handlers and callbacks.

Conclusion

Channels also do come at a cost, both in terms of performance as well as design complexity. Furthermore channels couple goroutines, and coupling introduces complexity, which in turn reduces re-usability. Use of channels should be given more thought.

--

--

arjun dhar
arjun dhar

Written by arjun dhar

Software development enthusiast since I was 8 yrs old. Love communicating on anything regarding innovation, community development … ∞

No responses yet