←back to thread

175 points nateb2022 | 2 comments | | HN request time: 0.406s | source
Show context
__MatrixMan__ ◴[] No.41522335[source]
I like that this is a thing, I'm trying to decide how excited I should be about it...

How equivalent are BEAM processes and goroutines?

replies(2): >>41522815 #>>41523150 #
kbolino ◴[] No.41522815[source]
Most significantly: goroutines are not addressable. This means that, from application code, you cannot send messages directly to a goroutine [1], you cannot link two goroutines, you cannot terminate a goroutine from another goroutine, you cannot directly watch a goroutine's exit [2], and there's nothing akin to the process dictionary (there are no "goroutine-local variables").

[1]: you usually use channels instead

[2]: you usually use sync.WaitGroup (or similar, e.g. errgroup.Group) instead

replies(2): >>41522875 #>>41540231 #
1. pancsta ◴[] No.41540231[source]
> you cannot send messages directly to a goroutine

Goroutines communicate through channels, all you need is a queue (eg buffered chan).

> you cannot terminate a goroutine from another goroutine

Termination is propagated via context cancellation. go-A cancels ctx, go-B waits with `select`, reads from `<-ctx.Done()` and does a `return`, or checks it after each blocking call.

> there are no "goroutine-local variables"

Not sure if I got this one, but every var in a function's scope, which has been `go`-routined, would qualify.

I'm currently working on a lib/framework somehow related to Ergo, but taking a more "generic" approach of a state machine[0]. It may solve some of the mentioned issues with Go, like addressing and queues for communication.

You seem to be very attached to an idea of using the same goroutine for a long time, whereas it's usually more dynamic and only schedulers are long lived `go`-s.

[0]: https://github.com/pancsta/asyncmachine-go

replies(1): >>41549757 #
2. kbolino ◴[] No.41549757[source]
Please note that the context of these points is in comparison to BEAM processes, as used by Erlang and Elixir. It is a comparative analysis of goroutines, not an holistic critique of them.

I appreciate the mention of context cancelation, that is a good example of how to terminate a goroutine, provided that a) the goroutine has a context to cancel and b) the goroutine does not spend too much time in operations that can't be canceled (e.g., file system calls, time.Sleep, blocking select without case <-ctx.Done(), etc.). This is still cooperative multitasking though, while BEAM processes are fully preemptive.

A "goroutine-local variable" would be a variable accessible by other goroutines which is still scoped to a particular goroutine. Ordinary local variables are only accessible by the goroutine in whose stack they belong. Something like the process dictionary can be constructed in Go using a synchronized map with atomic values, but it certainly isn't a built-in part of the runtime like it is in BEAM.

I generally don't regard goroutines with much attachment. There are, however, some situations in which goroutines must be long-lived: the main goroutine, the "scheduler" goroutines you mentioned, and in general any goroutine performing a supervisory role over other goroutines. Monitoring these important goroutines is more complicated in Go than BEAM languages, but certainly isn't impossible.

The more problematic aspect of goroutines vs. BEAM processes is that, due to the lack of application-accessible preemption, when a critical-path goroutine does get stuck, the only real solution most of the time is to kill the entire program. This is rarer than it was in Go's early days, at least.