Channels in Go are not a function, they are a primitive type.
In contrast to semaphores aka. mutexes, channels are highly recommended since, when used correctly, they can serialize concurrent access very efficiently. Take care of how to use them - do not pass tiny amounts of work over channels, pass around a chunk of work and work in batches.
Normally the work you will do per item will greatly exceed the 90–250 ns it takes to move the item through the channel, so it’s just not worth worrying about.
Channels are slower than copy() for just shifting bytes, but in a simple scenario are about as fast as a naive self-made channel implementation using the sync package.
The choice of channels vs. mutexes is one of design, not implementation. BOTH use LOCK XCHG and pay the price.
Also, channels are blocking data structures. APIs should be designed synchronously, and the callers should orchestrate concurrency if they choose. If you just want to synchronize access to shared memory then use a mutex. These are not good use-cases for channels to begin with.
As they say: Share memory by communicating, rather than communicating by sharing memory.
An easy example of this: Use a channel with a buffer of 1 to store the current number, fetch it from the channel when you need it, change it at will, then put it back for others to use.
But yeah, sometimes things break. We rely on things like nginx options to retry GET requests and idempotency in the design; failing gracefully (via a shutdown callback to always return something to a caller); ensuring work is completed before writing a successful response anywhere, along with being idempotent; and, ensuring there’s observability in every long-running task.
Exactly.
> Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.
> These functions require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.
There was a long mailing list discussion discussing use cases and certain desired behaviours that the people on the list agreed upon. In the end they chose not to make those behaviours explicit and leave it somewhat open-ended and instead put in the phrase "great care" to cover it off, rather than define it.
I found it while using sync.atomic to make some implementation faster, then wanted to know exactly what the language/runtime behaviour/guarantees were and was surprised but not shocked to find the 'for the go authors but not thee' attitude of the conclusion. It did have some reasonable founding where they mentioned that by formally defining it, it could be over-specified precluding future optimizations on even newer processors/architectures.
I would have preferred if the documentation explicitly listed the parts that are not fully defined with reasons for doing so given. That's better than a generic danger signpost. The irony here is that Go was supposed to be not-Java but has the same 'for mainstream' mindset rather than being the sharp tools for the sharp developers.
"Channel ops are wait free if the buffer has extra space though, so that should be fast"
But, I looked it up and channels indeed use locks even in case of buffered ops. Sigh. I guess MPMC with scheduler yield isn't the easiest thing to write and maintain.
If you've ever tried to doing any actual high-performance work using the Go mantra "share by communicating" with channels, you'll quickly find that it isn't high performance. Any Go program that actually needs high performance will break all the rules for thee and use undocumented things like sync/atomic.