Readit News logoReadit News
conradludgate · 3 years ago
Can the discussions here try to stay away from Go bashing. This post is not about Go. It's about Structured concurrency VS background tasks.

There's many interesting discussions one can have about the latter but the former turns into toxicity.

With that said. The Rust teams are very interestes in structured concurrency at the moment. Rust 1.63 is going to get scoped threads, which is structured concurrency for threading. Myself and others have also been looking into structured async, albeit it's not as easy to implement.

I personally love the concept and I hope it takes off. You rarely need long running background tasks. If you do, you probably want to have a daemon running along side your main application using structured primitives and then dispatch work to them instead. This really simplifies the mental model when you get used to it.

tialaramex · 3 years ago
Structured async sounds like a very interesting idea, this is definitely a space where it feels intuitively as though there should be a better way to express what we mean and get roughly the same machine code but better human understanding for both author and future maintenance programmers - if somebody nails this it could be as big a deal as the loop constructs in modern programming.
ignoramous · 3 years ago
> I personally love the concept and I hope it takes off

I have used structured concurrency with Kotlin and you are right, it is absolutely easier to reason about concurrent code that way.

https://elizarov.medium.com/structured-concurrency-722d765aa...

pca006132 · 3 years ago
Structured programming can guarantee that the control flow graph has constant treewidth[0], which enabled the use of parameterized algorithms for efficient static analysis. Wondering if structured concurrency can impose some additional constraints on the ordering of tasks that makes it easier to analyze, e.g. for linearizability or other properties.

[0]: Mikkel Thorup. 1998. All structured programs have small tree width and good register allocation

jpgvm · 3 years ago
The OpenJDK team is also persuing structured concurrency so we should have multiple interpretations shortly to compare. Exciting stuff for folks that write highly concurrent software.
zasdffaa · 3 years ago
But should they be writing highly concurrent software, without thinking bloody hard first I mean?

I can see this being the next Big Data or NoSQL 'must have' bandwagon.

andrewl-hn · 3 years ago
Swift has task-based structured concurrency since last year, and it looks really nice. https://developer.apple.com/wwdc21/10134
conradludgate · 3 years ago
Very interesting. Thanks for sharing!
_ph_ · 3 years ago
Interesting article, which gives some good idea how to structure concurrent programs with less shooting at your feet :). A bit long winded until it comes to the actual core concept being presented: spawn concurrent routines within a block which doesn't exit until the last routine has exited. This is a good concept an can make a lot of code clearer. It prevents some data races, as you can reason that after the block no concurrent operations are still ongoing, but of course it could make the block lock up by itself, if one of the routines doesn't finish. This is certainly an interesting concept I might try out myself more specifically. It is also important to know, especially as the "go" statement of the language Go is put into the headline, that this very language already supports nursery-like structures, just doens't have the syntax sugar the python extension of the author has.

It is called a wait group. See for an example here: https://gobyexample.com/waitgroups

So, except for the syntactic sugar around it, the nurseries compare to creating a wait group in a block, spawn goroutines which each call Done() on the wait group on exit and at the end of the block call Wait() to errect the same boundary, nurseries do. Of course you can also pass the waitgroup object to any goroutine created inside goroutines. This is a very common pattern in Go, but indeed, it probably should be presented more clearly and up front in tutorials about goroutines.

So for that, I will keep the article around, it shows the concept nicely - perhaps I might do a pure "go" version of it, which then shows the go implementation of nurseries. Might be nice to add to the original article, that not only the presented python library is a solution, but also that there is a native go way of achieving that, as the article uses "go" as the negative example :p

conradludgate · 3 years ago
I think you missed the point of the article. Its suggesting that goroutine style concurrency should be completely replaced.

Much like if/for etc has replaced goto, structured concurrency can replace goroutines. Note: goto can be used like an if or a for loop. You've just made the argument that goroutines can be used like 'nurseries'. You've essentially argued in favour of goto from the articles perspective

I want to note again that this article is not actually about go. It is the same in most languages with concurrency primitives.

The key takeaway is that having to manually manage waitgroups is room to make mistakes or to introduce spaghetti concurrency, while you might be used to it at this point, it doesn't make it the best system for the future of concurrency

_ph_ · 3 years ago
No, I didn't miss the point of the article. I just feel, it is important to note that the proposed style of concurency is available in go and point out to how you would implement it, if you cannot use this Python library.

I would agree, it would be nice to have a similar syntactic construct in go to enforce this pattern, though it is actually possible to basically implement that with higher order functions.

And yes, I think goto has a place in any language, if you are aware of how you should and how you shouldn't use it. In most cases the typical constructs of structured programming are preferrable, but not in all.

drwiggly · 3 years ago
Its not the same. Anything in the nursery can cause cancellation of all async work. Cancellation is safe to unwind in all workers by scope exit rules.

Completion is just a type of cancellation.

Spawn 10 workers to lookup Dog in 10 different dictionaries, the first one to get the answer wins. This is hard to do with out language/runtime cancellation support.

Note below is a practical lib to get close to this https://blog.labix.org/2011/10/09/death-of-goroutines-under-... .. https://github.com/go-tomb/tomb/tree/v2

swagonomixxx · 3 years ago
You can easily cancel worker goroutines in Go using contexts and their associated cancel functions (listen on ctx.Done() in a select statement).
duped · 3 years ago
> This is hard to do with out language/runtime cancellation support.

It is trivial if the workers can communicate using a shared flag that represents "Dog is found."

freiherr · 3 years ago
This was also my immediate thought, wait groups and a channel for any error would work just like the nursery. However I still see the advantage for this new approach as a provider for sane defaults. Right now you need to structure the concurrent routines yourself and are free to mess up. Nurseries should make it the other way around. I'm not sold on that but may give it a chance.
d0mine · 3 years ago
At best, waitgroups are to the structured concurrency as gotos are to loops/ifs(structured programming)--you can implement the latter in terms of the former but the structured part is better (even if/because it is less powerful) as the history shows.
wbl · 3 years ago
Waitgroups are not the same thing as they cannot be dynamically sized in the same way nurseries can.
swagonomixxx · 3 years ago
Not sure what you mean by this. You can dynamically add to a wait group using the Add() method.
evanmoran · 3 years ago
I have been using go professionally for a while and I’ve found it to be quite remarkable and I wanted to share a few hints to people so they can find those remarkable parts faster than I did.

To get a better sense of go there are five essential concurrency features:

1. Go statements are a nice syntax to run background micro threads (as mentioned in this article)

2. Go Channels pass messages across those threads as fixed memory queues and powerfully as you add items to the queue you block until the item is added (the second part, blocking on add, is very powerful as it prevents the flow of concurrency from infinitely filling queues that are never consumed! There is more to go channels that is possible with fixed memory buffering to prevent the block, but that blocking is key to consider!)

3. Go Select Statements (not switch statements!) let you watch concurrent queues at once in a thread safe way. These are essential for using channels properly and they help manage almost all channel flow (consumer thread progression, error handling, done detection, etc)

4. Go Context objects let you cleanup background threads based on multiple criteria server errors and timeouts being the most common. The best hint you are in a concurrent-friendly function is usually that it passes context as an argument for cleanup purposes.

5. Go wait groups let you wait for all the go statements spawned to finish before proceeding (simple, but essential at times)

I know it’s hard to learn the entirety of a langue without using it daily, but I encourage people to try out go to experience these five parts together. Go is truly excellent at expressing some hard concepts well. That doesn’t make it easy — concurrency isn’t easy — but it is easier with go constructs than without.

t_mann · 3 years ago
> Go statements are a nice syntax to run background micro threads (as mentioned in this article)

That's literally the opposite of what the article says. Which btw isn't about Go specifically, but about concurrency in general.

freiherr · 3 years ago
Well go statements are as nice as you can get with old-school concurrency primitives. Author of the article believes we need to have new completely concurrency primitives.
dang · 3 years ago
Related:

Go Statement Considered Harmful (2018) - https://news.ycombinator.com/item?id=26509986 - March 2021 (82 comments)

Notes on structured concurrency, or: Go statement considered harmful - https://news.ycombinator.com/item?id=16921761 - April 2018 (230 comments)

atoav · 3 years ago
In releated news:

"Considered Harmful" Essays Considered Harmfulhttps://meyerweb.com/eric/comment/chech.html

ghoward · 3 years ago
I can't take a hypocritical article seriously.
fijiaarone · 3 years ago
Brilliant solution, terrible name.

Took me a while to get it — child processes belong in nurseries. Bad abstraction, because the key here is processes. Lots of thing haves child nodes.

And what happens in nurseries? Growing? Maybe they were thinking watching — babysitting and it’s a cultural terminology difference.

But it would be just as silly to call a thread monitor/manager a babysitter as a nursery.

Like I said, it’s a great concept and a valuable abstraction, but I fear it will need a better name to take off.

avgcorrection · 3 years ago
> Took me a while to get it — child processes belong in nurseries. Bad abstraction, because the key here is processes.

Abstractions have nothing to do with their names. They are not good/bad based on what they are called. You might be conflating metaphors/analogies with that.

Avtomatk · 3 years ago
I hear the term "threadset" in other discussion about structured concurrency, i think "threadset" would be a better name.
SPBS · 3 years ago
A nursery is just an errgroup (https://pkg.go.dev/golang.org/x/sync/errgroup). I almost never have to use the `go` keyword directly, only through errgroups. Now I can see that it's because `go` is usually too low level to be used on its own. Not sure I agree with removing `go` entirely though.
pphysch · 3 years ago
The article does not make clear whether the cases where Go struggles with concurrency are also cases where structured concurrency improves the situation.

Pretty sure "sync.WaitGroup is too hard to reason about" is not a real issue people are having.

AFAIK most of the challenges occur within that structured block, so to speak. Robust communication between concurrent processes is the hard part, not managing their basic lifecycle.