
Goroutines and APIs - gnoack
https://blog.gnoack.org/post/go-goroutines-and-apis/
======
galkk
I think that it is universal advice for libraries: do not do multithreading
inside, let the client code do it and/or pass necessary constructs.

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...](https://ayende.com/blog/159201/multi-threaded-design-guidelines-
for-libraries-part-i)

~~~
jillesvangurp
I think what Kotlin is doing with co-routines improves a bit on this. The
approach there is to make asynchronous code look (almost) like normal code.
The only difference is that 1) you are dealing with functions that are marked
as suspend and 2) you can only call these from a co-routine scope.

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).

~~~
hn_mattfreeman
Kotlin's approach is extremely nice, not perfect but nice, for example most of
Kotlins collection operations are inline so functions like mapIndexed etc..
you can call a suspend function even though mapIndexed is agnostic (by nature
of just a ~template) as long as your in a co-routine scope of course, I don't
often call suspending operations in collection operations but the inline fun
approach well in other areas (your own code). Other languages don't have this
- Javascript for example map/filter/reduce won't await if you return a
Promise. C# would need specialized Task aware version of the Linq stuff (they
probably already exist).

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.

------
asdfasgasdgasdg
The blog seems sadly unable to tolerate the load. I don't see an archived
version of the post anywhere either.

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.

~~~
jjbiotech
Doesn't the native net/http server spin up a goroutine for every request?

Edit: See line 2927 at
[https://golang.org/src/net/http/server.go](https://golang.org/src/net/http/server.go)

~~~
asdfasgasdgasdg
Yeah, but it doesn't spin up a thousand for every request. And it doesn't
require you to start them. It starts them under the covers as part of the API.

~~~
jjbiotech
Right. So:

* Don't require users of your API to start [additional] goroutines to use your API correctly.

~~~
asdfasgasdgasdg
I guess what I mean to say is: don't make your users write

    
    
        go yourpackage.YourApiCall()
    

in order to use the API correctly. That's just a guess, though.

~~~
sagichmal
The article says almost exactly the opposite, and I agree.

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.

------
boomlinde
In general, decouple libraries from use case dependent assumptions. This holds
true not just for concurrency, but for memory management, sockets, file
streams etc. Don't assume that a per-object heap allocation is good enough for
me. Don't assume that I want to serialize to a file system. Don't assume that
a channel has a large enough buffer.

------
ridiculous_fish
Some operations can use green threads, while others need kernel threads. Last
I tried (~2015) Go would spawn unlimited kernel threads, so function callers
had to know whether a call required a kernel thread or not, and rate-limit
appropriately.

Is this still the case? How does modern Go handle operations which require
kernel threads?

~~~
sagichmal
Which operations need kernel threads? There is runtime.LockOSThread if you
need underlying thread affinity, but I'm not aware of any other mechanism to
interact with the underlying threads of execution.

~~~
ridiculous_fish
For example os.Stat()

~~~
sagichmal
Interesting, I've never encountered a situation where that kind of thing
matters, out of curiosity what are your use cases?

------
AndrewStephens
I can't read the article but hopefully it does not ironically present advice
on writing webservers.

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.

~~~
gnoack
The Raspberry Pi handles the load well after fixing that configuration
mistake. (I still need to investigate to understand in detail what went wrong
there...)

------
kimi
The problem with goroutines is that an unhandled crash can cause a shutdown,
and if the goroutine is started by a badly-written library with no proper
recovery, you are dead.

~~~
Thaxll
Panic don't shutdown the entire program, only panics on the main goroutine. If
your HTTP servers panics it will just kill the goroutine handling the request.

------
kstenerud
"threads" (goroutines) and channels are the new gotos. They tend to make a
codebase more difficult to reason about. The more you add, the more you tend
to get spooky action at a distance.

~~~
zerotolerance
These are hardly new concepts, nor are there any more appropriate mechanisms
for modeling dependencies between multiple concurrent actors. If anything, the
traditional shared memory nightmare has proven to be the goto in the room.

~~~
lostcolony
Funny you should say actors. While some people will equate the two, I find
Erlang actors easier to reason about than go channels. Though that has
everything to do with monitors and links, to tie dependent
goroutines/processes together.

