On Concurrency in Go HTTP Servers
eli.thegreenplace.net
eli.thegreenplace.net
Channel sends and receives have non-trivial costs associated with them.
When you choose to model your problem with channel communication, you better be sure you’re doing enough work before and/or after the send/recv to justify involving the scheduler.
I’ve reviewed code where new engineers spawn unbounded goroutines... for each goroutine to then go on to compute the distance between two points on a two dimensional plane... aggregate the results on a channel and compute a sum.
I believe performance is only a small part of the problem, channels in Go are just too awkward to use.
The idiom of defining message types that you post to a channel so that you can pull them from the channel and execute them strikes me as boilerplate. What you really want to do is run some code in a particular thread context; so why not just do that directly?
Are there useful things you do with channels that can’t easily be done with thread pools?
> While it certainly looks like an interesting technique, for our particular use case this approach seems like an overkill. In fact, overuse of channels is one of the common gotchas for Go beginners. Quoting from the Go Wiki entry:
> > Most locking issues can be solved using either channels or traditional locks.
> So which should you use?
> Use whichever is most expressive and/or most simple. It then goes on to suggest that mutexes are preferable for protecting shared state. I tend to agree, since a mutex feels more natural here.
Either the app is a vanilla evented io web application, in which case you could write it in pretty much any major language using an evented framework. Or it's a mess of buggy channel usage.
I guess there's a happy balance of "let the library maintainers do the hard channel work" but to be honest I can use someone else's high performance evented io library in any language.
Regarding libraries, best practice is to use CSP sparingly and to design the API as blocking calls so the applications have the choice of using the call directly or wrapping it with CSP.
If so, it feels to me like channels are a bit of a misfeature. They work OK as a way to implement concurrency, sure, but not in a way that you want to expose in your API. They’re not very composable.
(That’s a bit hand-wavy as I’m not a heavy Go user. If there are good uses of channels in public APIs I’d be interested to hear about them.)
Compare to e.g. maps, or closures. Those are very generally useful and composable primitives and it’s therefore common to see them used directly in APIs.
Yes, it is pretty rare. The only example I know off the top of my head is the ssh library, but I would say it is an appropriate and good use of them and you can look at that as a possible example.
https://godoc.org/golang.org/x/crypto/ssh
> If so, it feels to me like channels are a bit of a misfeature. They work OK as a way to implement concurrency, sure, but not in a way that you want to expose in your API. They’re not very composable.
If you just look at channels and goroutines without some knowledge of CSP they will look a bit rough and low level, but once you understand CSP you'll see they are one of the better primitives available today. IMO the main problem with them is that people try to just use them without really understanding how to use them and it is pretty easy to shoot yourself in the foot with them.
But there are rare cases where you may want to offer them. I find a pattern I often have is to officially provide a method or set of methods that do whatever the thing is, but if you have a sophisticated case where you may need the thing the library is doing to participate in a select, you can offer a channel. There's often additional responsibilities for the caller that come with it when I do that. But it's more efficient that asking them to spawn a brand new goroutine just to call my code that is just going to drain a channel for them. Some particularly advanced fixtures may even have their own internal plumbing bits, but you normally shouldn't care about those.
Channels are fairly composible, it's just that it shouldn't be libraries doing the composing. To use a rough physical equivalent, your sink, dishwasher, toilet, and shower don't come with their own pipes to hook into the system, because whoever made those things have no idea what your local concerns will be. Fixtures are the library functionality, and the channels are the local plumbing. It's not a perfect metaphor, since physical objects intrinsically fix more aspects than code does, so don't pick at it too hard, it's just something to get an idea across.
(They'll get more composable with the new generics stuff, when it comes out. Although possibly dangerously so; generic channel libraries are going to make it easy to compose yourself up some local plumbing that involves way too many hidden goroutines and channels and will make it easy to trash performance.)