Live data from Hacker News

Goroutines Under the Hood (2020)

osmh.dev

21–30 of 55 posts

Re: Goroutines Under the Hood (2020)

#21

Earlier quoted context omitted.

> And indeed, Go's scheduler is cooperative. It hasn't been cooperative for a few versions now, the scheduler became preemptive in 1.14. And before that there were yield points at every function prolog (as well as all IO primitives) so there were relatively few situations where cooperation was necessary. > Without knowing too much assembly, I would assume any modern processor would make a context switch a one instruc…

> It hasn't been cooperative for a few versions now, the scheduler became preemptive in 1.14. And before that there were yield points at every function prolog (as well as all IO primitives) so there were relatively few situations where cooperation was necessary. Since co-op was most unnecessary, do you know why it was changed to preemptive or what the specific cases were that are resolved with preemptive scheduling?

Tight loops without function calls.

Re: Goroutines Under the Hood (2020)

#23

Earlier quoted context omitted.

> It hasn't been cooperative for a few versions now, the scheduler became preemptive in 1.14. And before that there were yield points at every function prolog (as well as all IO primitives) so there were relatively few situations where cooperation was necessary. Since co-op was most unnecessary, do you know why it was changed to preemptive or what the specific cases were that are resolved with preemptive scheduling?

Tight loops without function calls.

IIRC in earlier versions, an infinite loop without function calls could freeze the entire runtime: GC's stop the world event is triggered => goroutines are being suspended cooperatively => but this one goroutine never enters a function prolog and thus never suspends => all goroutines except one are suspended and there's no progress. Preemptive scheduling is much more robust. Although it's solvable in cooperative scheduling with an additional suspension check at the end of each loop, but it adds overhead for all loops. If I remember correctly, .NET or JVM implement safe points for GC (which can be used to switch contexts cooperatively as well) by simply reading a pointer from a special preallocated virtual memory page which is remapped to nothing when a stop-the-world event is triggered, so such a read traps into an invalid memory handler where you can park your thread. But I'm not sure how costly it is for thousands/millions of coroutines.

Re: Goroutines Under the Hood (2020)

#24
I've fallen in love with Python's asyncio for some time now, but I know that go has coroutines integrated as a first class citizen.

This article (which I have not read but just skimmed) made me search for a simple example, and I landed at "A Tour of Go - Goroutines"[0]

That is one of the cleanest examples I've ever seen on this topic, and it shows just how well integrated they are in the language.

[0] https://go.dev/tour/concurrency/1

Re: Goroutines Under the Hood (2020)

#25

Earlier quoted context omitted.

> It hasn't been cooperative for a few versions now, the scheduler became preemptive in 1.14. And before that there were yield points at every function prolog (as well as all IO primitives) so there were relatively few situations where cooperation was necessary. Since co-op was most unnecessary, do you know why it was changed to preemptive or what the specific cases were that are resolved with preemptive scheduling?

Tight loops without function calls.

Replace “tight” with “long-running/infinite” but yeah, otherwise this is correct.

Re: Goroutines Under the Hood (2020)

#26
post #24

I've fallen in love with Python's asyncio for some time now, but I know that go has coroutines integrated as a first class citizen. This article (which I have not read but just skimmed) made me search for a simple example, and I landed at "A Tour of Go - Goroutines"[0] That is one of the cleanest examples I've ever seen on this topic, and it shows just how well integrated they are in the language. [0] https://go.dev/…

Having used both in production for many years, Go’s model is waayyyy better, mostly because Python’s model results in a bunch of runtime bugs.

The least of which are type error things like forgetting to await an async function—these can be caught with a type checker (although this means you need to have a type checker running in your CI and annotations for all of your dependencies).

The most serious are the ones where someone calls a sync or CPU-heavy function (directly or transitively) and it starves the event loop causing timeouts in unrelated endpoints and eventually bringing down the entire application (load shedding can help mitigate this somewhat). Go dodges these problems by not having sync functions at all (everything is async under the covers) and parallelism means CPU-bound workloads don’t block the whole event loop.

Re: Goroutines Under the Hood (2020)

#27

I love Go and goroutines, but... > A newly minted goroutine is given a few kilobytes a line later > It is practical to create hundreds of thousands of goroutines in the same address space So it's not practical to create 100s of Ks of goroutines - it's possible, sure, but because you incur GBs of memory overhead if you are actually creating that many goroutines means that for any practical problem you are going to wan…

If you asked me what “a few kb” times “hundreds of thousands” is, I’d have characterized it as “more than a few hundreds of thousands of kb”, not necessarily “gigabytes,” and that doesn’t sound impractical at all. My JVM heaps are usually 16GB.

And go actually does a pretty good job of scheduling hundreds of thousands of threads. 6 months ago I did some fairly abusive high-thread-count experiments solving problems in silly ways that required all N goroutines to participate and I didn’t see much perf falloff on my laptop until I got 1.5-2 million goroutines.

Re: Goroutines Under the Hood (2020)

#29
post #8
post #2

I mean it has many diagrams and logical explanation of Goroutines and concurrency concepts in general but it is definitely not under the hood descriptions.

I came here to write the same thing. Things I had hoped this would go into is how goroutines grow their stacks, and how they are preempted.

There you go: Vicki Niu, Goroutines: Under the hood, https://www.youtube-nocookie.com/embed/S-MaTH8WpOM (2020).

Re: Goroutines Under the Hood (2020)

#30
post #23

Earlier quoted context omitted.

Tight loops without function calls.

IIRC in earlier versions, an infinite loop without function calls could freeze the entire runtime: GC's stop the world event is triggered => goroutines are being suspended cooperatively => but this one goroutine never enters a function prolog and thus never suspends => all goroutines except one are suspended and there's no progress. Preemptive scheduling is much more robust. Although it's solvable in cooperative sche…

> But I'm not sure how costly it is for thousands/millions of coroutines.

Still cheap: you only need to preempt the threads which are actively running user code. If a coroutine is ready to run, but not actually running, you don't have to do anything with it (as long as you check for safepoints before entering user code.) That means your safepoints cost is `O(os threads currently running user code)` which in most runtimes is `O(num cores)`

Post reply on HN