Live data from Hacker News

Coroutines for Go

research.swtch.com

21–30 of 191 posts

Re: Coroutines for Go

#21
post #19
post #13

Earlier quoted context omitted.

Sure but that's what the implementation of the coro in this post uses under the hood. Not sure how this is any better wrt overheads.

> Not sure how this is any better wrt overheads. At the end he implements an experimental runtime mechanism that permits a goroutine to explicitly switch execution to another goroutine rather than using the generic channel scheduling plumbing.

Its in the last paragraph of the article...Very easy to miss given the code uses goroutines and channels.

Re: Coroutines for Go

#22
It looks like a lot of people are missing the point here. Yes a coroutine library would be a worse/more cumbersome way to do concurrency than the go keyword.

The use case motivating all the complexity is function iterators, where `range` can be used on functions of type `func() (T, bool)`. That has been discussed in the Go community for a long time, and the semantics would be intuitive/obvious to most Go programmers.

This post addresses the next thing: Assuming function iterators are added to the language, how do I write one of these iterators that I can use in a for loop?

It starts by noticing that it is often very easy to write push iterators, and builds up to a push-to-pull adapter. It also includes a general purpose mechanism for coroutines, which the adapter is built on.

If all of this goes in, I think it will be bad practice to use coroutines for things other than iteration, just like it's bad practice to use channels/goroutines in places where a mutex would do.

Re: Coroutines for Go

#23
post #16

Earlier quoted context omitted.

Race conditions. With coroutines you’re not supposed to deal with race. If i am understanding the motives

You can write to a channel concurrently.

And to make the concurrency safe you have to pay the price of synchronization.

https://go.dev/tour/concurrency/8

In A Tour of Go, concurrency section, "Equivalent Binary Trees" is an example of paying the price when you don't need it.

Re: Coroutines for Go

#24
post #20
post #17

Earlier quoted context omitted.

Where did you get this '..it uses same under the hood'? The article clearly says: ..Next I added a direct coroutine switch to the runtime, avoiding channels entirely. That cuts the coroutine switch to three atomic compare-and-swaps (one in the coroutine data structure, one for the scheduler status of the blocking coroutine, and one for the scheduler status of the resuming coroutine), which I believe is optimal given…

The runtime switch was buried in the last paragraph of the article. All of the code was using goroutines and channels....

It does mention in an earlier section:

... That means the definition of coroutines should be possible to implement and understand in terms of ordinary Go code. Later, I will argue for an optimized implementation provided directly by the runtime,..

Re: Coroutines for Go

#25
I'm not 100% sure this is the case, but I believe the context of this goes something like this. As Go has added generics, there are proposals to add generic data structures like a Set. Generics solve almost every problem with that, but there is one conspicuous issue that remains for a data structure: You can iterate over a slice or a map with the "range" keyword, and that yields special iteration behavior, but there is no practical way to do that with a general data structure, if you consider constructing an intermediate map or slice to be an insufficient solution. Go is generally performance-sensitive enough that it is.

The natural solution to this is some sort of iterator, as in Python or other languages. (Contra frequent accusations to the contrary, the Go community is aware of other language's efforts.)

So this has opened the can of worms of trying to create an iteration standard for Go.

Go has something that has almost all the semantics we want right now. You can also "range" over a channel. This consumes one value at a time from the channel and provides it to the iteration, exactly as you'd expect, and the iteration terminates when the channel is closed. It just has one problem, which is that it involves a full goroutine and a synchronized channel send operation for each loop of the iteration. As I said in another comment, if what is being iterated on is something huge like a full web page fetch, this is actually fine, but no concurrency primitive can keep up with the efficiency of incrementing an integer, a single instruction which may literally take an amortized fraction of a cycle on a modern processor. With generics you can even relatively implement filter, map, etc. on this iterator... but adding a goroutine and synchronized commit for each such element of a pipeline is just crazy.

I believe the underlying question in this post is, can we use standard Go mechanisms to implement the coroutines without creating a new language construct, then use the compiler under the hood to convert it to an efficient execution? Basically, can this problem be solved with compiler optimizations rather than a new language construct? From this point of view, the payload of this article is really only that very last paragraph; the entire rest of the article is just orientation. If so, then Go can have coroutine efficiency with the standard language constructs that already exist. Perhaps some code that is using this pattern goroutine already might speed up too "for free".

The concerns people have about this complexifying Go, the entire point of this operation is to suck the entire problem into the compiler with 0 changes to the spec. Not complexifying Go with a formal iteration standard is the entire point of this operation. If one wishes to complain, the correct complaint is the exact opposite one, that Go is not "simply" "just" implementing iterators as a first class construct just like all the other languages.

Also, in the interests of not posting a full new post, note that in general I shy away from the term "coroutine" because a coroutine is what this article describes, exactly, and nothing less. To those posting "isn't a goroutine already a coroutine?", the answer is, no, and in fact almost nothing called a coroutine by programmers nowadays actually is. The term got simplified down to where it just means thread or generator as Python uses the term, depending on the programming community you're looking at, but in that context we don't need to use the term "coroutine" that way, because we already have the word "thread" or "generator". This is what "real" coroutines are, and while I won't grammatically proscribe to you what you can and can not say, I will reiterate that I personally tend to avoid the term because the conflation between the sloppy programmer use and the more precise academic/compiler use is just confusing in almost all cases.

Re: Coroutines for Go

#26
post #9
post #6

Not sure if this really is required. Most cases in Go are served well by GoRoutines and for yield/resume semantics, 2 blocking channel are enough. This seems to add complexity for the sake of it and not sure it actually adds any new power to Go that already didn't exist.

Goroutines + channels add an enormous amount of overhead. Using them as iterators is basically insane.

It’s not insane at all. How did you come to that conclusion?

* Mutex lock+unlock: 10ns

* Chan send buffered: 21ns

* Try send (select with default): 3.5ns

Missing from here is context switches.

In either case, the overhead is proportional to how fast each iteration is. I have channels of byte slices of 64k and the channel ops don’t even make a dent compared to other ops, like IO.

You should absolutely use channels if it’s the right tool for the job.

Fwiw, I wouldn’t use channels for “generators” like in the article. I believe they are trying to proof-of-concept a language feature they want. I have no particular opinion about that.

Re: Coroutines for Go

#27

what? I'm a Go newb, but isn't this what goroutines and channels get you?

This is an interface that can be implemented in terms of goroutines and channels, but can also be implemented with a lower-overhead scheduler tweak. The article shows how it could be implemented using goroutines and channels, and then reports the result of that implementation versus an optimized version that avoids synchronization overhead and scheduler latency which is unnecessary with this pattern.

Currently, you could use goroutines and channels to implement a nice way to provide general iteration support for user-defined data structures, but because of the overhead, people most often opt for clunkier solutions. This change would give us the best of both worlds.

Re: Coroutines for Go

#28
post #16

Earlier quoted context omitted.

Race conditions. With coroutines you’re not supposed to deal with race. If i am understanding the motives

You can write to a channel concurrently.

its not that you cant, it's that its very expensive.

Re: Coroutines for Go

#29
post #9
post #6

Not sure if this really is required. Most cases in Go are served well by GoRoutines and for yield/resume semantics, 2 blocking channel are enough. This seems to add complexity for the sake of it and not sure it actually adds any new power to Go that already didn't exist.

Goroutines + channels add an enormous amount of overhead. Using them as iterators is basically insane.

see also https://github.com/golang/go/discussions/54245

Re: Coroutines for Go

#30
post #7

Earlier quoted context omitted.

Garbage collector in Go is optional. You can switch off garbage collection by setting the environment variable GOGC=off. More info about GOGC: https://dave.cheney.net/tag/gogc

Ok so garbage collection is optional, how about garbage generation? Is there any way to manually clean up resources if GOCG=off, or will memory usage continue to grow unbounded as new objects are created?

I think in theory you can write code (or do some tricks) to avoid all heap allocation.
Post reply on HN