Buffered Channels in Go: Tips and Tricks
rapidloop.com
rapidloop.com
Edit: An interesting application of a buffered channel, for example, is when creating an object pool of finite size. Or, in conjunction with a timer, rate limiting a piece of code. I don't think I have ever considered a buffered channel in the typical producer/consumer setup.
In degenerate cases that means that one of the cadences is inappropriately slow but in most cases it rounds out latency spikes and increases throughout.
For that matter I very rarely use zero size channels because if 2 things are on the same cadence why should I make them concurrent?
Most people's intuitions about work distribution aren't very good either; if you break a process up into 5 pieces, your human brain will tend to estimate the five pieces as roughly the same size ("within an order of magnitude of each other"), when in the real computer world they will almost always be separated by multiple orders of magnitude in size. Evenly breaking up a task is usually quite hard and not something that happens accidentally. It doesn't help you all that much to use a 10-element channel if the producer is producing an element every 100 nanoseconds while the consumer is consuming one element per 10 milliseconds, both reasonable numbers, but separated by 5 orders of magnitude. The advantage of using a buffered channel is lost in the noise of the consumer, and it's not hard for it to be a net loss given the complexities buffered channels can involve if you accidentally code as if they are asynchronous rather than buffered-but-still-ultimately-synchronous. Zero-sized channels are more likely to exhibit any errors you made during development or QA rather than in production.
I don't think I've ever shipped a non-zero-sized channel in Go. Occasionally designs go through phases where they may have them, but they always come back out before I'm done. Even the "only allow X of a certain resource to be used" always seems to turn into "only spawn X goroutines that use that resource and feed them work off a channel that has no reason to be buffered".
I'd probably make a lot more use of empty channels if go had more sophisticated primitives around that. If I could say, publish to this channel until a timeout without all the overhead that is currently required.
As it stands (on my workloads), with unbuffered channels its hard to figure out if you have encountered a localized short term full channel or you've achieved some more degenerate case where you should be taking drastic load shedding action. Buffered channels make rough approximations of this very easy. Under normal operating modes this shouldn't be full. If it is sound the klaxon and begin load shedding.
And for most things that aren't pipelines, I simply don't think channels are very appropriate.
I had to read about this and I'm a little dumbfounded. Why wouldnt Go support timeouts for channels? The extra complexity seems huge especially if you wanted a timeout that was different than the library based hack -- for example if you wanted a timeout since last write.
I don't understand what the Go devs are protecting people from.
Or if you want a 30 second timeout since the last read? Way more complex.
Go's channels seem more than a little under developed.
On the publish side in most production cases you don’t want to use time.after as it leaks. So you’ll use either a context or a timer.
All told it’s a fair bit of boilerplate to get right everytime (which you can’t genericize) which is why I tend to use buffered channels that are sized such that being full is a degenerate case.
I agree that a small buffer can smooth out latency issues on the consumer side only, and that requires both sides to be reasonably similar in frequency of the latencies are disproportionately large compared to the unit of work. But that requires a lot of live metrics to capture.
I still believe that unbuffered is the way to go.
If you have 1:1 producer/consumer and they are doing 1:1 events to output, almost always I think people have gone concurrent when they shouldn't.
Does this mean the producer have to keep pointer for the queued item around or it could get garbage collected? That seems.. unintuitive? Or is it just that working with the object could make the old pointer invalid?