47 ms·
Coroutines for Go
- jerf 3y agoI'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.
- adrusi 3y agoA goroutine really is implemented as a coroutine though. It's just that without this runtime optimization, there hasn't been a way to interact with them without letting a multithreaded scheduler manage their execution. I don't remember where I came across this, but many years ago I saw python-style generators termed "semi-coroutines" which I'm a fan of. Python (frustratingly) doesn't implement them this way, but the beauty of generators is that by limiting the scope where execution can be suspended to the highest-level stack frame, you can statically determine how much memory is needed for the (semi-)coroutine, so it doesn't require a single allocation. Zig takes that a step farther by considering recursive functions second-class, which lets the compiler keep track of the maximum stack depth of any function, and thereby allow you to allocate a stack frame for any non-recursive function inside of another stack frame, enabling zero-allocation full coroutines, as long as the function isn't recursive. That would... probably be overkill for Go, since marginal allocations are very cheap, and you're already paying the runtime cost for dynamic stack size, so the initial allocation can be tiny. I would love to see full proper coroutine support make it to Go, freeing users of the overhead of the multithreaded scheduler on the occasions where coroutine patterns work best. I remember back in 2012 or so, looking at examples of Go code that showed off goroutine's utility as control flow primitives even when parallelism wasn't desired and being disappointed that those patterns would likely be rare on account of runtime overhead, and sure enough I hardly ever see them.
- silisili 3y agoNot sure I'm a fan. Looking through the examples, I feel like this makes the language much harder to read and follow, but maybe that's just my own brain and biases. Further, it doesn't seem to me to allow you to do anything you can't currently do with blocking channels and/or state.
- JyB 3y agoYou’re absolutely right. People advocating for it can’t seem to see beyond their nose.
- slantedview 3y agoMore likely they're trying to solve real problems you just haven't hit yet.
- iends 3y agoThe go team has explicitly said this is the past: “You just don’t understand because you’re not at Google’s scale.”
- philosopher1234 3y agoWhere have they said this specifically?
- hgsgm 3y agoYes, the person who spent 15 years developing the Go language can't see beyond his nose.
- hgsgm 3y agoA dozen different incompatible iterator implementations (the current stdlib) isn't easier. Channels are slow for no good reason when they aren't wrapping blocking operations.
- enneff 3y agoWhat language change are you talking about? This is just a proposed construct to regularise and make efficient something people already do (as you says with “state”). I’ve used iterators similar to what’s described in this article to avoid allocations in critical code paths, but this would make those much less awkward to use (particularly with the upcoming range iterator language change).
- RcouF1uZ4gsC 3y agoI don’t think Coroutines would fit in with Go. There is a huge emphasis on simplicity. Coroutines add a massive amount of complexity. In addition, goroutines provide the best parts of Coroutines - cheap, easy to use, non-blocking operations - without a lot of the pain pints such as “coloring” or functions and issues with using things like mutexes. Just the question of whether one should use a goroutine or a coroutine adds complexity.
- badrequest 3y agoThere are plenty of complicated things in Go, IMHO where it shines best is judiciously providing incredibly nice interfaces atop the complicated things.
- bb88 3y agoHaving coded in go professionally, I disagree. Go abstractions leak in weird and unexpected ways that are surprisingly different to it's C/C++/Java predecessors. Goroutines were kind of the raison detre' for using go. But using them wasn't simple, and instead often goroutines brought their own issues. See here: https://songlh.github.io/paper/go-study.pdf https://songlh.github.io/paper/go-study.pdf Often a programming language takes a first guess at the problems they want to solve, and often get them wrong. C++ is probably the most notable language in this category here. That said, I do appreciate an attempt to improve programming languages even if it undermines the primary feature of the language itself.
- talideon 3y agoThere's no colouring here. These are synchronous coroutines.
- djha-skin 3y agoI thought that the entire point of green threads was so that I didn't have to use something like Python's `yield` keyword to get nice, cooperative-style scheduling. I thought go's `insert resumes at call points and other specific places` design decision was a very nice compromise. This is allowing access to more and more of the metal. At what point are we just recreating Zig here? What's next? An optional garbage collector?
- vaastav 3y agoGarbage 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 https://dave.cheney.net/tag/gogc
- gkfasdfasdf 3y agoOk 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?
- vaastav 3y agoGrows unbounded. I wasn't recommending that one should set GOGC=off. Just making a remark that one could should they choose to do so. EDIT: Sorry, I misunderstood part of your question. The memory grows unbounded unless you call runtime.GC() which triggers garbage collection. But this is a blocking call and essentially block your whole program.
- Cthulhu_ 3y agoI think in theory you can write code (or do some tricks) to avoid all heap allocation.
- dgb23 3y agoYou mean not growing the heap or literally no allocation?
- HumblyTossed 3y agowhat? I'm a Go newb, but isn't this what goroutines and channels get you?
- adrusi 3y agoThis 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.
- EspressoGPT 3y agoYou can build this yourself using goroutines and channels, but adding "native" first-class support for this generator pattern would be easier to use and come with less overhead.
- vaastav 3y agoNot 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.
- masklinn 3y agoGoroutines + channels add an enormous amount of overhead. Using them as iterators is basically insane.
- rcme 3y agoWhy? Channels are already iterable using the range keyword. ch := make(chan int) go func() { for i := 0; i < 100; i += 1 { ch <- i } close(ch) }() for i := range ch { fmt.Println(i) } That is very simple.
- Zach_the_Lizard 3y agoI have written Go professionally for many years now and don't want to see it become something like the Python Twisted / Tornado / whatever frameworks. The go keyword nicely prevents the annoying function coloring problem, which causes quite a bit of pain. Sometimes in high performance contexts I'd like to be able to do something like e.g. per CPU core data sharding, but this proposal doesn't scratch those kinds of itches.
- cookiengineer 3y agoCan you maybe share hints to better channel management patterns/frameworks? Usually my goroutines related code feels like a mess because of them, and I don't know how to make them "more clean" (whatever that means). Any hints proven to be maintainable patterns would be highly appreciated.
- Zach_the_Lizard 3y agoI don't always use channels in the code I write. For example, let's say I have []foo as input and I'm going to call a service on every element of the slice. You could do it with channels, but oftentimes I'll reach for creating a slice of results and passing each goroutine an index into the slice. That way I can avoid any overhead that comes from having what is effectively a queue with a lock. It depends on what I'm doing, how I want to treat failures of a single goroutine, etc. of course. I default to never returning channels unless it's a requirement for the use case (e.g. context.Done() returns a channel so callers can listen for when the context is closed). Callers can always create goroutines to make your code run concurrently. It's more annoying to erase channels when they aren't useful to the caller (e.g. if you made all functions that make HTTP calls return a channel because you assume callers want a new goroutine, but I already have my own goroutine per request) Beyond that, some specific examples would be helpful so I can tailor my advice
- GaryNumanVevo 3y ago> The go keyword nicely prevents the annoying function coloring problem FYI when you write a goroutine and have to use Context, that's literally just function coloring
- alphazard 3y agoIt 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.
- deleted 3y ago[deleted]
- djha-skin 3y agoStill a kitchen sink move, though, isn't it? Like, no careful thinking and good 80/20 solution this time. Just "huh, we'd need coroutines to do this `right` so let's just do that" When they added generics, they really, really thought long and hard, and came up with a compromise that was brilliant and innovative in the balance it struck. I would have hoped to see something like that here, like "we're adding this one feature to goroutines to have control in specific situations" feels like something that would be better than "we're going full Rust on this problem and just adding it."
- morelisp 3y agoThis has been under discussion for a long time. https://github.com/golang/go/issues/43557 https://github.com/golang/go/issues/43557 https://github.com/golang/go/discussions/56413 https://github.com/golang/go/discussions/56413 I'm not sure Russ's personal blog is any kind of official statement "this is what we're doing" yet?
- ketchupdebugger 3y agoI'm not sure why author is advocating for single threaded patterns in a multithreaded environment. Not sure why he's trying to limit himself like this. The magic of goroutines is that you can use all of your cores easily not just one. Python and Lua has no choice.
- yakubin 3y agoCoroutines are a control-flow mechanism. They're a single-threaded pattern in as much as for loops are a single-threaded pattern. Ability to write multithreaded programs does not exclude the need for good single-threaded tools.
- ketchupdebugger 3y agoLooking at python's asyncio coroutine library, they are just mocking multithreading with asyncio.gather. Since coroutines can be executed in any order they are not really control-flow mechanisms. The selling point of coroutines over traditional threads is its lightweight but its moot since goroutines has similar memory cost to coroutines. The only real benefit is that coroutines are non blocking while goroutines may be blocked. There is no real benefit of having python's coroutine in go since goroutines does the same but better.
- yakubin 3y agoI don't know about Python, but this description of coroutines, the general concept, doesn't seem accurate to me. They most definitely cannot be executed in any order. They also have nothing to do with multithreading whatsoever. Lua has the most honest-to-god implementation of coroutines I know of, so I'd suggest looking at that. I've seen the word "coroutines" used in some weird way suggesting multithreading, which probably means Python used them to fake multithreading, but the idea originally has nothing to do with it. The idea actually started out as a way to better structure an assembly program in 1958: <http://melconway.com/Home/pdf/compiler.pdf http://melconway.com/Home/pdf/compiler.pdf>.
- maxk42 3y ago
- VWWHFSfQ 3y agoAside: Lua is an absolute work of art. Everything about the tiny language, how it works, and even all the little peculiarities, just makes sense.
- bovbknn 3y ago[flagged]
- dingxiong 3y agohmm. why are Lua arrays 1-index based?
- MathMonkeyMan 3y agogood enough for Rome
- nmz 3y agoBecause arrays start at 1, offsets start at 0
- defrost 3y agoArrays are just a sequence of objects. Indexes (traditionally) start at 1, offsets (measures from base) obviously start from zero.
- cakoose 3y agoOne core Lua thing that I think is an ugly mistake: trying to represent maps (dictionaries) and arrays using a single logical data type. Most languages use different data types but with some API overlap, e.g. maps and arrays are both "iterable". Lua goes too far, I think, and tries to make them the exact same, a data type they call "table". One side-effect is that you have some operations that only really make sense for maps or lists, but since they work on all tables, they're defined awkwardly, e.g: > The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).
- bovbknn 3y ago[flagged]
- xwowsersx 3y agoSomewhat on topic given that OP brought up coroutines in Python: what resources have folks used to understand Python's asyncio story in depth? I'm just now finally understanding how to use stuff, but it was through a combination of the official documentation, the books "Using Asyncio in Python" and "Expert Python Programming", none of which were particularly good. Normally I'd rely just on the official docs, but the docs have created much confusion, it seems, because there's a lot in them that are useful more so for library/framework developers than for users. So, I'm just wondering if anyone has great resources for really gaining a strong understanding of Python's asyncio or how else you might have gone about gaining proficiency to the point where you felt comfortable using asyncio in real projects.
- manifoldgeo 3y agoI read the same books you did, and I was equally unsatisfied afterwards. The "Using Asyncio in Python 3" book was good enough to help me write some code that had to hit an API 400k times without blocking, but I never returned to asyncio after that. Afterwards, I realized there was a package called aiohttp that I could've used, but too late. I'll be interested to see what other HN people have done.
- dingxiong 3y agoThis blog helps me a lot about the motivation and underlying mechanism of python asyncio https://tenthousandmeters.com/blog/python-behind-the-scenes-12-how-asyncawait-works-in-python/ https://tenthousandmeters.com/blog/python-behind-the-scenes-...
- xwowsersx 3y agoThanks a lot, I'll check that out.
- chrsig 3y agoCoroutines are one thing that i'd probably prefer language support for rather than a library. x := co func(){ var z int for { z++ yield z } } y := x() for y := range x { ... } or something to that effect. It's cool that it can be done at all in pure go, and I can see the appeal of having a standard library package for it with an optimized runtime instead of complecting the language specification. After all, if it's possible to do in pure go, then other implementations can be quickly bootstrapped. My $0.02, as someone that uses go at $work daily: I'd be happy to have either, but I'd prefer it baked into the language. Go's concurrency primitives have always been a strength, just lean into it.
- 38 3y agowow that is horrible. none of that is intuitive, which is one of Go strength. you would need to specific learn the Go semantic of coroutine to have any chance of writing or even reading code like this.
- sk0g 3y agoGoroutines and channels aren't intuitive either. You learn them and become familiar with them over time, at which point they become intuitive __for you__.
- 38 3y agoactually I get it now. the issue is that OP was giving TWO different examples of use, pulling a single value versus multiple. they should have clarified.
- chrsig 3y agoYes, on review, I should have made two examples instead of combining them. Thanks for persevering through my brevity and reaching understanding :)
- dboreham 3y ago
- pierrebai 3y agoThe examples given prompt me to say: if all you have is Rube-Goldberg hammer, everything looks like an Escheresque nail. Sieving primes by turning functions into coroutines, parsing text by yielding characters, all with unnatural functions and state management... that;s an improvement over what?
- MathMonkeyMan 3y agoMultitasking systems gave us processes. But those were too much. So we got threads, which are processes that share an address space, file table, and some other things. The scheduler can switch from one to the other more easily than between processes, and data can be shared between threads without needing serialization. But those were too much. So we got user space threads, which are logical threads of execution that are driven by a runtime entirely in user space. The runtime adds scheduling hooks into all I/O functions in the standard library, or even uses a system API like Unix signals to preempt logical threads. No system-level context switching is needed. User space threads can be tiny. But those were too much. So we got coroutines, which allow a programmer to define logical "threads" of execution that cooperatively interact with each other. There is no assumption about the presence of a scheduler. The programmer either writes their own event loop or invokes one from a library in a "real" logical thread. I wonder what comes next. As far as [communicating sequential processes][1] are concerned, maybe cooperative coroutines are a low as you can go. [1]: https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf
- earthboundkid 3y agoI don’t think there’s any further down to go, but there’s probably room to go up to distributed computing. IIRC, some early versions of Go when it was in alpha had channels work across machines.
- davenoul 3y agoI think there is. How do you describe data parallelism? coroutine are too big when all your function call are doing the same things, but on a different data (think ISPC, shaders, cuda). There is still one more step in parallelism where coroutine cost to much :-)
- earthboundkid 3y agoGood point. SIMD routines!
- deepsun 3y ago
- metadat 3y agoReasoning about and following the control flow of the proposed code hurts me inside. If Go adds function coloring via (e.g. python's async and/or yield concepts), I'm out, because I don't want to use this, much less encounter it in the form of a bug in some library. Java and C++ are largely inferior for my typical purposes, but at the end of the day they work fine and are stable in terms of direction, and don't tend to repeatedly bloat the language over pedantry. If you want top-notch performance, there's already C, C++, and Rust. I am not a fan of the function coloring shit in Python and Javascript. I don't want the kitchen sink!
- andrewshadura 3y agoYield has nothing to do with colouring.
- pmarreck 3y agoAs a point of comparison, here's my demo from a recent presentation of firing up 1 million (1,000,000) Elixir (BEAM VM) threads, sending them all a "Hello!" message, and then each thread waits a random amount of time between 0 and 2 seconds to send a message back of "Process <their number> received message <themessage>!" At the same time, I am running the Erlang observer beside it to watch what happens to the CPU and memory consumption and how quickly it recovers/cleans up the garbage. The biggest bottleneck here is the terminal's ability to keep up, but the observer seems to reflect what's happening accurately. https://www.youtube.com/watch?v=yxyYKnashR0 https://www.youtube.com/watch?v=yxyYKnashR0 The code I used: https://gist.github.com/pmarreck/4cc8f2f55a561ebce2012085a3a631f0 https://gist.github.com/pmarreck/4cc8f2f55a561ebce2012085a3a... These features have been built into Erlang (and thus Elixir) since the 1980's. I'm sure many of you have heard of the Actor model and/or Erlang's "legendary" implementation of it, but I don't know how many have actually seen it in action with monitoring kit running. I think it would be great for Go if it offered language-level support like this, but given the extremely resource-efficient implementation (both in spawning and runtime consumption) of threads on the BEAM VM, coupled with the ease of concurrency which comes directly from only permitting immutable values, I don't think it will ever be matched.
- xpressvideoz 3y agoReading the comments makes me feel bittersweet. - Many people consider coroutines and green threads to be more or less the same thing, when they both have their pros and cons. - The fact that the omission of iterators is even acceptable in the Go community saddens me. They seem to deliberately refuse any feature that might make the language even slightly more complex, in the name of simplicity. But hey, at least they retracted their opinion on generics. I'm again reminded that Go is not my language.
- 38 3y agoI use Go daily. honestly generics didn't change my code much at all. I use them now somewhat, but only because they offer slightly better sugar at the call site. at the definition site, they are just as ugly as you expect, granted the Go syntax is about the best I have seen from other languages. so in the end I think we really lost some simplicity in order to silence the whining about no generics. not a good tradeoff.
- earthboundkid 3y agoI wouldn’t confuse HN with “the Go community”. The head of the Go team wrote this post. Will the coro package proposed in the post be added exactly as written? Maybe, but probably not exactly as written. Will something like it be added? I would be willing to bet money on it, yes. How long will it take? I’d say at least a year (release in Go 1.23 in August of 2024), maybe a little longer. I don’t think it could be much shorter though.
- deleted 3y ago[deleted]
- Groxx 3y agoGiven Go's history, there's a moderately high chance that this will be in a PR and immediately merged within the next few hours. They're a bit bipolar on "listen, wait, debate" vs "we just wrote about a new thing, it's already built and in stdlib but is largely undocumented"
- 3y ago
- samsquire 3y agoThis is a thoroughly interesting topic. Thanks for the article. I haven't thought much about iterators link to coroutines. As a hobby, I am working to write about a dream programming language. I happen to be really interested in parallelism, asynchronous, coroutines, multithreading and concurrency. I want: * seamlessly switch between remote-thread coroutine, local thread coroutine. * concurrency and parallelism and async to be easy to think about, reason about, read and program * programs should be easy to parallelise and be async and concurrent Go iterators seem to be local to a thread, but what if you want to distribute work across threads? I've been thinking of scheduling recently. Imagine you're a search engine company and you want to index links between URLs. How would you solve this with coroutines? task download-url for url in urls: download(url) task extract-links parsed = parse(document) return parsed task fetch-links for link in document.query("a") return link task save-data db.save(url, link) How would you do control flow and scheduling and parallelism and async efficiently with this code? * `db.save()`, `download()` are IO intensive whereas `document.query("a")` and `parse` is CPU intensive. * I want to handle plurality or multiple items trivially such as multiple URLs and multiple links. * I want to keep IO and CPU in flight at all times. I think I want this schedule: https://user-images.githubusercontent.com/1983701/254083968-b46485c8-fe5f-43ea-b840-d0d63dab4a51.PNG https://user-images.githubusercontent.com/1983701/254083968-... I have a toy 1:M:L 1 scheduler thread:M kernel threads:N lightweight threads lightweight scheduler in C, Rust and Java https://github.com/samsquire/preemptible-thread https://github.com/samsquire/preemptible-thread This lets me switch between tasks and preempt them from user space without assistance at descheduling time. I have a simplistic async/await state machine thread pool in Java. My scheduling algorithm is very simple. I want things like backpressure, circuit breakers, rate limiting, load shedding, rate adjustment, queuing.
- kragen 3y agoi've been thinking about a closely related feature in a different context: adding block arguments, as in smalltalk or ruby or especially lobster, to a language more like c, with static types and stack allocation i think this would be favorable for (among other things) clu-like iterators and imgui libraries, where you often want to do something like submenu("&Edit") { command("&Cut") { clip_cut(getSelection()); } ... } this is especially useful in a context where you're heap-allocating sparingly or not at all, because the subroutine taking the block argument can stack-allocate some resource, pass it to the block, and deallocate it once the block returns; python context managers and win32 paint messages are two cases where people commonly do this sort of thing, but things like save-excursion, with-output-file, transactional memory, and gsave/grestore also provide motivation the conventional way to do this is to package up the block into a closure, then use a full-fledged function invocation to invoke it, using a calling convention that supports closures. but i suspect a more relaxed and efficient approach is to use an asymmetric coroutine calling convention, in which the callee yields back control to its caller at the entry point to the block, and the block then resumes the callee when it finishes. so instead of merely dividing registers into callee-saved and call-clobbered, as subroutine calling conventions do, we would divide them into callee-saved upon return but upon yield containing callee values the block must have restored upon resumption; caller coroutine context registers, which are callee-saved upon return and also on yield; and call-clobbered. you also need in many cases a way for the block to safely force an early exit from the callee this allows the caller's local variables to be in registers its blocks can use without further ado, or at least indexed off of such a register, while allowing the yield and resume operations to be, in many cases, just a single machine instruction. and it does not require heap allocation as an example of taking this to the point of absurdity, here's an untested subroutine for iterating over a nul-terminated string passed in r0 with a block passed in r1, using a hypothetical coroutine convention which passes at least r4 through from its caller to its blocks itersz: push {r6, r7, r8, lr} mov r7, r0 mov r6, r1 1: ldrb r0, [r7], #1 cbz r0, 1f blx r1 b 1b 1: pop {r6, r7, r8, pc} and here is another untested subroutine which uses it to calculate a string hash hashsz: push {r4, r5, r9, lr} movs r4, #53 adr r1, 1f blx itersz mov r0, r4 pop {r4, r5, r9, pc} 1: eor r4, r0, r4, ror #27 bx lr even in this case where both the iteration and the visitor block are utterly trivial, the runtime overhead per item (compared to putting them in the same subroutine) is evidently extremely modest; my estimate is 7 cycles per byte rather than 4 cycles per byte on in-order hardware with simple branch prediction, so, on the order of 1 ns on the hardware russ used as his reference. for anything more complex the overhead should be insignificant it's less general than the mechanism russ proposes here (it doesn't solve the celebrated samefringe problem), but it's also an order of magnitude more efficient, because the yield and resume operations are less work than a subroutine call, though still more work than, say, decrementing a register and jumping if nonzero
- pjmlp 3y agoI guess it is great that they are finally paying attentio to programming languages like CLU. On the other side, given my experience with .NET and C++ co-routines, and Active Objects (in Symbian C++ and Active Oberon) not sure if this is really something to add to Go. Even the .NET team has acknowledged at this year's BUILD, that if they could go back in time having the runtime handle them Go-style would probably been a better decision, given how many developers keep having issues understanding async/await.
- FZambia 3y agoWondering whether coroutines may be a step towards async event-based style APIs without allocating read buffers for the entire connection. I.e. a solution to problems discussed in https://github.com/golang/go/issues/15735 https://github.com/golang/go/issues/15735. Goroutines provide a great way to have non-blocking IO with synchronous code – but when it comes to effective memory management with many connections Go community tend to invent raw epoll implementations: https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/ https://www.freecodecamp.org/news/million-websockets-and-go-.... So my question here – can coroutines somehow bring new possibilities in terms of working with network connections?
- up2isomorphism 3y agoThe most valuable quality of a programming language committee is holding the temptation to add any new features unless it is something that drives existing users away.