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

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.



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.


> 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...


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


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.


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.


> But should they be writing highly concurrent software, without thinking bloody hard first I mean?

Yes, by necessity. We're 15 years after the end of the race for frequency, and the end of moore's law is getting ever closer. Concurrency and parallelism are becoming table stakes for both reactivity and performances. This means making them reliably usable and composable is getting more and more important.


@ohgodplsno (edit, and now @jpgvm's) have good answers grounded in a specific scenario which really makes their point well, but yours worries me.

>> But should they be writing highly concurrent software, without thinking bloody hard first I mean?

> Yes, by necessity.

So we should not think about it first, just do it?

And not consider whether there are better solutions such as checking we're using the right algos or cache-aligning our accesses or using a probabilistic filter before hitting the DB or..

Edit: no offense intended, the original question was meant as a rhetorical device to warn against dumbness and bandwaggoning. I am sure you too would not plough in unthinkingly but I'm afraid our industry's reaction is too often to do just that and it's such an expensive mistake, repeated over and over. It's a thoughtless jumping ahead that will lead to slower software, not faster. And buggier.


I think you might be confusing parallelism with concurrency somewhat. Writing highly parallel software is only important in certain domains. However concurrency is pervasive due to I/O, you will struggle to find a domain that does no I/O (especially one that doesn't benefit from parallelism).

Structured concurrency is about managing concurrent operations with less cognitive overhead. The most common case being handling stuff like cancelation in a reasonable way. These problems are relevant everywhere I/O is relevant which I would wager is most programs people write and interact (see I/O!) with on a daily basis.

Should you be using concurrency without thinking? Well ofcourse not but if you aren't using it there is a very good chance you are "doing it wrong" for which wrong is some approximation of providing a poor user experience or writing software that makes inefficient use of resources and/or wall-clock time.

On the topic of highly parallel software however I do mostly agree, there is a few things that benefit from highly parallel architectures but generally it's going to make whatever you are doing way harder. Some exceptions are forms of limited parallelism like making use of SIMD in tight loops, this is great because it doesn't require you to make your program logic parallel, data operations just execute in parallel on multiple inputs on a single thread.

Anyways. My point is almost all programs need some form of concurrency whilst most don't need parallelism. Concurrency is hard and has a multitude of competing solutions (async/await, threading w/monitors + atomics + friends, CSP) while parallelism is generally even more difficult but has much more limited solution space whist having some overlap in terms of abstraction layers.


> I think you might be confusing parallelism with concurrency

Very good point, I hadn't got myself straight on that, thanks


I do in fact plow unthinkingly into using concurrency quite frequently, and it works out quite well. I just unthinkingly use techniques like "just throw it in a dedicated thread", "put each stage in a thread, move objects between threads via channels", and "Pin one thread each to N cores, distribute incoming events across threads" whenever they seem like they might make a thing good, and they keep working out really great pretty much every time.

If you're writing C, you're going to have a bad time, but we've built some really great tools with modern type systems that make it far easier to treat concurrency as a thing that you can just rely on being able to safely use.

When you're using a language with misuse-resistant core primitives, like structured concurrency, and like Rust's Ownership, Mutex, and Send/Sync traits, it really is a meaningfully different programming experience. You make small use of concurrency all the time, because you just know by default without investing any time at all to check that you haven't made any kind of dumb mistakes.

When you use concurrency all the time, and get instant feedback from the compiler describing the precise data dependency that would make your idea a dumb choice, you get a ton of real direct feedback to learn about how to use concurrency correctly, and what designs it's a good fit for.

I agree that concurrency isn't a replacement for checking for algorithmic improvements, profiling and tuning your memory access patterns, using probabilistic filters, caching, etc. But just like how you can just unthinkingly drop a probabilistic filter before hitting a DB, I think you can and should be able to just unthinkingly spread a bunch of work out across a bunch of cores. This should be a simple, obvious, normal thing that people do by default whenever they care at all about performance, and it can be with good safe tools.


I’m excited about concurrent Kubernetes clusters to compile our JavaScript frameworks to run on virtualized mainframes and achieve roughly equivalent performance and functionality of a pascal program running on an 8086 in 1985.


Serious note, I have worked on interfaces in pascal on a 80286 in that period, and the UI performance was crisp.


> And not consider whether there are better solutions such as checking we're using the right algos or cache-aligning our accesses or using a probabilistic filter before hitting the DB or..

It's mostly for io-intensive workloads. And for nicer debugging experience compared to callback hell. Nothing to do with NoSQL, or right algos, or cache-aligning when you just need to do 10M http requests/db-inserts/rpcs as fast/efficient/nicer-dev-experience as possible.


> So we should not think about it first, just do it?

It is much easier to bake concurrency and parallelism from the start than to retrofit it in an existing sequential project, even more so as it avoids sub-optimal patterns.

> And not consider whether there are better solutions such as checking we're using the right algos or cache-aligning our accesses or using a probabilistic filter before hitting the DB or..

Why are you declaring that those are incompatible with concurrent programs, exactly?


Well I don't know about most people but to speak for myself I mostly write high performance network servers, databases and queues and in my world concurrency (if not parallelism) is strictly necessary. For me it's also not a recent development or a fad, it's been this way my entire ~15ish year career.

I imagine folks writing Web software, UIs and heavy desktop applications will also benefit from these developments but those areas are out of my core expertise so I can't speak to exactly how useful structured concurrency will be for them however I can see very clear applications for my domain.


Yours is definitely not the case I was disputing.


If you do any kind of UI work, you are already considering carefully what to run on the UI thread (unless you're using JS but then you brought this upon yourself). Additionally, many reactive patterns require you to collect a flow and this blocks the whole thread. Launching a coroutine on a background dispatcher is safe and simple.

There is a middle ground before highly concurrent, and it's "I don't really care what you do, but please use the cores i can have and don't block the main thread thank you"


As elsewhere, yours is definitely not the case I was disputing but as UIs are not my area in the way you are clearly familiar with them, a question: What kind of application are you doing your UI for that can use all the cores?


Yep. The other response has it pretty right. I don't really care that i use _all_ the cores. Ideally i have one or two dedicated to IO, some for general computation, but most importantly i need things to get the fuck out of the main thread as fast as possible. User clicks somewhere, offload any computation to a coroutine on the background. If you don't do this, your UI will die. Even small, 50ms calculations are dreadful if ran on the UI thread. That means 3 dropped frames.

Structured concurrency just means that i can both not really care where it goes, be generous on coroutines (I have only screwed up once, and that was by having hundreds of threads deadlocking on database access. Otherwise, you most likely have... 50 running at worst?). Structured concurrency also means that the moment i drop out of a screen, every computation launched in the context of this screen and not a higher level is just dropped immediately, meaning it runs just when I need it to


What kind of _small_ computation are you doing that requires 50 whole milliseconds?


You'd be surprised how easy it is to find junior devs iterating over a whole list to find something by its ID instead of using a hashmap.

Actually not just juniors I also do it sometimes but shhh


It's not a matter of using all the cores. It's a matter of not oversubscribing the one core that is receiving user input and updating the UI in response. UI frameworks are still (for the most part) extremely single-threaded, and pretty much leave it up to you to offload all your computation - or murder your UI responsiveness


Swift has task-based structured concurrency since last year, and it looks really nice. https://developer.apple.com/wwdc21/10134


Very interesting. Thanks for sharing!




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

Search: