Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Everything can be a coroutine (goroutine) in Go, very simple. Just add `go` before the function call. No need to do `async` and `await` or try to make sure it's turtles all the way down by never calling a synchronous function from within a `async` function. In Go, everything within the function proceeds synchronously unless you use `go`.

It's literally as simple as it sounds:

https://gobyexample.com/goroutines

Want to wait for a bunch of parallel coroutines to finish (sync) before proceeding? Sure, that's easy too:

https://gobyexample.com/waitgroups

This simple primitive is one of two major go features that made me not really pursue Rust more, simply because Rust's concurrency libraries (it doesn't seem to be an intrinsic part of the language like Go) seem to be more modeled after Python's or Node's. Go's `go` is even more readable than Erlang and Scala, so in my opinion it's one of the great three features of the language

(OT, but to me the other two major features are 1: channels being an integral part of the language, and 2: forced immediate error handling -- perhaps you disagree that this second one is a feature and prefer tracebacks, but in my experience large Go projects always seem to have cleaner code than, say, Python, probably because errors are handled very close to their origination. Lastly, there are a minimum of footguns in Go.)



I've been in a go job recently, and I have to say, I hate the entire language - a lot - except goroutines. I normally complain a lot (in my head to myself, not to anybody else) about literally everything not being written in Java, but with for this service go was actually a really solid choice at the time. The green thread approach + writing "blocking" code is far superior to async/await or event-based code and the product wouldn't been a mess if it were using a less straightforward programming model.

But now that Java has the same feature in the form of virtual threads, I'm once again back to complaining!


Ok. But, surely there are places you could go and use Java.


Language choice is low on my list of reasons I choose a company, but there's no law that says I can't still complain to myself ;)


goroutines do not give the programmer control over suspension points, though, so they differ from coroutines as most people are apt to think of them. There was consideration for adding coroutine[1] support for the rangefunc experiment, but it didn't make the cut for 1.22, opting for a simpler model.

[1] https://research.swtch.com/coro


That's what the other great feature of Go, channels, are for.

https://softchris.github.io/golang-book/05-misc/04-goroutine...

Channels let you block (control suspension points) and wait for progress from any other thread.


That's not really a coroutine. It is a set of language features that can be used to solve similar problems in a different way, and while conflating those two things is a popular programmer past time, they aren't the same thing.

Coroutines are one of those terms that used to have a really price academic definition and has gotten a lot looser over the years. Python generators don't conform to the academic definition, but casually calling them coroutines is pretty popular now.

However, no matter how you slice it, goroutines aren't coroutines and that's not the derivation of the name. They're threads. Within those threads is bog-standard structured programming. All functions have one entry point. Being able to push out values through channels is no different than a normal function that writes several things to a file, it doesn't have the distinctive characteristics of a coroutine.


Python generators do conform to the academic definition of coroutines. Specifically, they are stackless, asymmetric, coroutines. Which is somewhat nerfed and second rate, in my opinion, but that's Python for you.

I agree with you, as anyone should, that goroutines aren't coroutines at all. Preëmptive scheduling is disqualifying. I'm confident that the cute name was intended to convey "these aren't coroutines at all" but that was somewhat lost in the translation.


> used to have really precise academic definition

Out of curiosity, what is the precise academic definition?

goroutines + channels were specifically designed to solve the concurrency/communication/locking/flow/performance problems that traditional coroutines introduced. Coroutines are much heavier and introduce a lot of overhead.

Goroutines are not necessarily threads; they're more akin to greenthreads. In Go, they're much cheaper and lower latency and still automatically move between processors (subject to your control).

But, aside from lower performance and greater difficulty in locking and multi-threaded communication via shared objects, coroutines are also much harder to code with and reason about.

The primitives in Go are very simple and that is one of the best features of the language. Running a for loop across a set of channels is very easy and built-in to the language, making it childs-play to write a heavily-concurrent network server. Channels are specifically designed to be a high-speed data bus between goroutines, rather than ever use more expensive and less safe shared memory. (Channels are preferred over shared memory in Go)

This is a fantastic video explaining how and why Rob Pike and team developed the idea of goroutines and channels, building on their work from Smalltalk: https://go.dev/blog/waza-talk


You are thinking about threads, not coroutines. Coroutines are not “heavy” and have nothing to do with concurrency or locking.


> Channels are specifically designed to be a high-speed data bus between goroutines, rather than ever use more expensive and less safe shared memory.

What do you mean? How do you think channels are implemented? Go is open source, you can see for yourself, channels still use mutexes internally.

https://github.com/golang/go/blob/b6ca586181f3f1531c01d51d63...

If you needed one mutex lock to receive a reference to a data structure, a channel does not make that cheaper. The absolute best case is that the same OS thread served both sides of the channel transaction, but you can't plan your overall performance around that, because it becomes less likely the more loaded your program's goroutines are. In general you still have to think of both the send and receive side of the channel as short-lived exclusive locks of the mutex, with all of the contention and cache effects that implies.

Shared memory is not more expensive. Memory is memory, it's either cached on your core or not. In fact, Go still has to issue fence instructions to ensure that the memory it observes after a channel read is sequenced after any writes to that memory, so it's at best the same cost you'd have with other forms of inter-thread communication in any language.

Anyway, even that is missing the point. Go still shares memory if you used a reference type, and most types in Go end up being reference types, because it's the only way to have a variable-sized data structure (and while we're at it, string is the only variable-sized data structure that's also immutable).

The bigger problem is that Go doesn't enforce thread safety. Channels only make communication safe if you send types that don't contain any mutable references... but Go doesn't give you any way to define your own immutable types. That basically limits you to just string. Instead people send slices, maps, pointers to structs, interfaces, etc. and those are all mutable and Go does nothing to enforce that you didn't mutate them.

Even if all of that somehow wasn't true, many parallelism patterns simply don't map well to channels, so you still end up with mutexes in many parts of real world projects. Even if you don't see the mutexes, they're in your libraries. For example, Go's http.Transport contains a connection pool, but it uses mutexes instead of channels because even the Go team knows that mutexes still make sense for many real-world patterns.

https://github.com/golang/go/blob/b6ca586181f3f1531c01d51d63...

This whole "channels make Go safe" myth has to stop. It's confused a generation of Go programmers about the actual safety (and apparently performance) tradeoffs of channels. They do not make Go safer (mutable references are still mutable after being sent on a channel), they do not make it faster (the memory still has to be fenced), and heck while we're at it, they do not even make it simpler ("idiomatic" use of channels introduces many ways that goroutines can deadlock, and deadlock-free use of channels is much more complicated and less idiomatic).

The most useful thing about channels is that you can select{} on multiple of them so they partly compensate for Go's limitations around selecting on futures in general. They're a poor substitute when you actually needed to select on something like IO, where io.Reader/Writer still don't interact with select, channels, or even cancellation directly.


> Coroutines are one of those terms that used to have a really price academic definition and has gotten a lot looser over the years. Python generators don't conform to the academic definition, but casually calling them coroutines is pretty popular now.

Could you elaborate on how Python's implementation doesn't meet the precise definition?


'goroutines' are just threads, that happen to be scheduled in userspace. They are not coroutines - they don't work the same way and don't have the same features.

Pretty much nothing in the blog post is possible is Go, without 'writing it yourself', which is what you are doing with channels. The same is possible in most other languages.


> No need to do `async` and `await` or try to make sure it's turtles all the way down by never calling a synchronous function from within a `async` function.

Yes, good grief this is annoying.


The irony of a language with first-class one shot continuations not having exceptions is not lost.


Which language is that?

Context would suggest you are referring to Go, but Go has exceptions. It even has exception handlers. As do all of the other languages mentioned.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: