zlacker

The Downsides of Go's Goroutines

submitted by djha-s+(OP) on 2023-12-31 04:34:52 | 23 points 31 comments
[view article] [source] [go to bottom]

NOTE: showing posts with links only show all posts
1. fithis+s6[view] [source] 2023-12-31 06:20:41
>>djha-s+(OP)
Very good discussion. Still, exceptions are dangerous.

https://pianomanfrazier.com/post/exceptions-considered-harmf...

2. keving+F7[view] [source] 2023-12-31 06:49:44
>>djha-s+(OP)
Externally canceling a task at a location other than a known stopping point is used as an example here, but in most environments doing this is a known-bad design decision, since the terminated thread-or-task might have been holding a mutex, and now that mutex is stuck closed forever. .NET has been closing the door on this primitive for years (https://learn.microsoft.com/en-us/dotnet/core/compatibility/...)
3. mratsi+69[view] [source] 2023-12-31 07:18:32
>>djha-s+(OP)
The coloring of go functions is actually seen in the FFI. Go functions have a special calling convention so FFI is annoying.

This is similar to Cilk back in 1995 with their continuation-stealing work-stealing scheduler. The C++ committee made an in-depth review of fibers (stackful coroutines that install their own stack).

Regarding scheme, the issue is that they have multishot undelimited continuations, which let to several debates. Delimited continuations are enough to express everything but non-determinism (which causes the lifetime issues, i.e. you can enter a room once and exit twice), in practice you allow continuations to be moved (on different executors like a state machine, threadpool or actors) but not copied.

Cilk: http://supertech.csail.mit.edu/papers/PPoPP95.pdf

Fibers under magnifying glass: https://open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1364r0...

on Scheme undelimited continuations: https://okmij.org/ftp/continuations/against-callcc.html#dyna...

In general, I've collected experiments and criticisms from many many language on continuations, coroutines, fibers, cancellation here: https://github.com/mratsim/weave-io-research/blob/master/res...

◧◩◪
5. crater+O9[view] [source] [discussion] 2023-12-31 07:30:30
>>verdag+k9
The Go folks will repeat the aphorism, "Do not communicate by sharing memory; instead, share memory by communicating."[1]. The author directly violates the intention of the designers of Go by talking about shared file handles and other data structures, i.e. memory.

The word "channel" doesn't appear a single time in the article, even though goroutines without channels to communicate with each other should never be sharing data. Channels are the synchronization primitive in Go.

1. https://go.dev/blog/codelab-share

6. EdiX+4b[view] [source] 2023-12-31 07:55:18
>>djha-s+(OP)
What a strange article. Goroutines definitely do have their downsides, they aren't hard to find either, but nothing he talks about here is actually a downside of goroutines. Also for some reason almost every reference he has is from 2016.

> Further, goroutines' lack of their stack ancestry means they can't natively give out the nice stacktraces found in other languages, though there are a lot of workarounds, to be sure.

I wish he was more speific here, what's "nice" about stacktraces in other languages that is lacked by Go? I really don't get it, it seems to me that they are the same, maybe I'm missing something. There's even a mechanism to record goroutine ancestry [1] but it's from 2018, past this article's knowledge cutoff date maybe?

> Because of this stack disconnection of parents and children, it really becomes impossible to have exceptions

But panics use the exact same mechanism as exceptions? They are not used in the same way as exceptions but it isn't because of any technical limitation of goroutines, it's mostly about what's idiomatic in the language.

His whole example about a panic causing a resource leak, as far as I can tell the difference is that in java an uncaught exception will only close its thread while in go the whole program crashes. You can definitely argue for both behavior as being better (IMO it really depends on the application), but it doesn't have anything to do with how goroutines are implemented.

> This highlights a key difference between cooperative scheduling and OS-level scheduling: OS-level threads can be stopped at any time, while cooperatively scheduled coroutines cannot

Goroutines are actually scheduled preemptively since 2020 [2], again this is probably beyond the knowledge cutoff date of the article. This isn't surfaced to the user however, you still can't stop a goroutine at an arbitrary point. But it doesn't have anything to do with the way goroutines are implemented, it's because it is a design decision to not allow users to do that.

[1] https://github.com/golang/go/issues/22289 [2] https://go.dev/doc/go1.14

11. tommie+Ab[view] [source] 2023-12-31 08:09:49
>>djha-s+(OP)
> Further, goroutines' lack of their stack ancestry means they can't natively give out the nice stacktraces found in other languages,

There's GODEBUG=tracebackancestors=N since 2018.

https://pkg.go.dev/runtime

> 3. Goroutine b panics, crashing the program. > > 4. The database connection is then left open as a zombie TCP connection. > > [---] Because of this stack disconnection of parents and children, it really becomes impossible to have exceptions.

I don't follow this reasoning. If goroutine b panics, the process dies and the TCP connection is closed by the OS. If you handle the panic in b, there's no zombie connection, since both a and b are still alive to handle the connection.

Exceptions are just return statements with pattern matching on stack unwind. Nothing special. I can't figure out what the author sees in them that I don't. The Go defer statements are perfectly capable of handling lifetimes, and a common situation is that you create a goroutine just to manage a resource's lifetime, e.g. when you have multiple producers and you need to close the channel after all of them are done.

> In Go, only calls made by the offending goroutine can recover from a panic, while in Java, the parent caller/creator of the new thread can itself set a recovery mechanism.

And in Go, the caller can insert a stub function that handles recovery before calling the main goroutine function. This seems equivalent to me.

> He says the mainframers at his company hate UnixODBC for this reason, it tends to leave zombie connections behind. (Go uses UnixODBC

I don't know how his dad's mainframe handles terminated processes, but this needs clarification on why they're left: the OS really should take care of closing those connections.

> Consider the problem of a task scheduler cancel button. The program must schedule a task to be run on an agent. At any given time, however, the task must be able to be cancelled.

Fair enough, that requires extra adaptation to Go (what the author calls workarounds.) It will never be as nice as simply killing a preemptive thread.

> In Java, it is child's play to interrupt a running thread.

And now the application programmer has to handle far more cases of partially complete tasks instead. The number of strawman arguments in this post is astounding.

[go to top]