Goroutines and APIs
blog.gnoack.org
blog.gnoack.org
Simple example for Java: do not create executorservice instances inside of a library, but allow client code to pass them.
Ayende wrote much better than me long time ago https://ayende.com/blog/159201/multi-threaded-design-guideli...
The last bit is the crucial thing. The co-routine scope is how you structure concurrency and is something I have not really seen in other approaches. In a mobile application you have the main thread, IO threads, and maybe some CPU threads. When calling something that is asynchronous, you have to assign it a scope. When that scope ends (because of a timeout, error, etc.) there's a cleanup step that cancels all the dangling tasks.
The nice thing about this is that you get clean separation of how to structure your concurrency and the logic. They also made it very easy to integrate with existing asynchronous code (there are a lot of frameworks for this in Java).
The scope tree thing is really nice too once you get your head around it, much nicer than in c# with carrying cancellationtokens around and creating your own cancellationsource when you need something like a supervisor scope.
If I had to have a guess what it says, it's probably something along the lines of:
* Don't require users of your API to start goroutines to use your API correctly.
If Goroutines need to be started for your API to work, start them in an init function, on first use, or via some other mechanism.
* If possible, avoid high goroutine fanout in your implementation.
If a single request to your API fans out to 1k goroutines, it's going to be a problem for a high-traffic user of your API. Especially if there are other APIs that make the same design choice. As ever, try to be parsimonious with resource consumption.
IMO, these are two good principles to live by, and not just in Go.
> Don't require users of your API to start goroutines to use your API correctly.
Actually, what I postulate in the article is different... The theory is that making the caller kick off the goroutines puts more control over concurrency into the hands of the caller, and the resulting API is simpler.
> Avoid high goroutine fanout in your implementation.
That's for sure. :)
Edit: See line 2927 at https://golang.org/src/net/http/server.go
Request handlers are a bit of a special case too, in that they are a framework for dispatching tasks to be worked on; what is main() for a command line program is the request handler for a webserver. It seems fair that there is some concurrency coordination happening at the top level.
* Don't require users of your API to start [additional] goroutines to use your API correctly.
go yourpackage.YourApiCall()
in order to use the API correctly. That's just a guess, though.Your API should present all of the things it does as synchronous functions/methods. If callers want to run them asynchronously, they can easily provide their own goroutine to do so, including whatever lifecycle management makes sense for them.
The concrete example was
// Do this
func (t *type) Run(ctx context.Context) { ... }
// Not this
func (t *type) Start() { ... }
func (t *type) Cancel() { ... }
This is generally good advice which stops a whole class of extremely common and very tricky to debug orchestration bugs outright.Is this still the case? How does modern Go handle operations which require kernel threads?
In my case, it was for running Go on a resource constrained Raspberry-PI, where the kernel threads could easily live too long, and use up all memory. The threads were calling read(2) on a network mounted fuse fs, and would last for 30s+.
There is really little excuse for a web server to buckle under pressure these days - hackernews sends a spike of traffic but not enough to kill even a moderately spec'ed server.
(Source: Am web server developer)
Many alternatives to channels (mutexes, atomics, semaphores, condition variables) have just as much if not more chance of turning the codebase into a mess. But yes I agree that if performance-wise you can handle being single-threaded, then do that, because it's much simpler.