Live data from Hacker News

Notes on structured concurrency, or: Go statement considered harmful

vorpus.org

51–60 of 234 posts

Re: Notes on structured concurrency, or: Go statement considered harmful

#51

This is very very long to explain a very simple pair of ideas: 1) You should be able to declare scoped blocks that mandate execution of all tasks started in that block ends when the scope ends. 2) This is fundamentally superior to all other forms of concurrency. I get it; this is basically what async/await gets you, but conceptually you can spawn parallel tasks inside an awaited block, and know, absolutely that all o…

I found it still an interesting read, but I agree with you. The most important problem is:

> Then our guarantee is lost: the operations that look like they're inside the with block might actually keep running after the with block ends, and then crash because the file gets closed while they're still using it. And again, you can't tell from local inspection; to know if this is happening you have to go read the source code to all the functions called inside the ... code.

But actually you can "tell", or even better have a type system good enough to prevent such mistakes entirely, and the author even knows a bit about Rust, yet fails to state that at least that item is a solved problem there (by using a different and arguably more general approach).

Now I don't know enough to decide whether something is missing on the panicking background thread front, but if it does that seems very solvable.

I don't buy that spawning threads (or even moral equivalents) and multiprogrammation is in a situation similar to unstructured use of goto anyway. You have tons of problems applicable to one and not the other. And of course resource management is hard to get right with threads. But we have at least a production example of a language that get it right on some points by leveraging more general ideas (and the other difficult points are mostly not addressed by the nursery idea, anyway).

Re: Notes on structured concurrency, or: Go statement considered harmful

#52
post #3

Earlier quoted context omitted.

I don't think its a superior paradigm, just a different one. I only see his nursery as being useful when you really want your async tasks to complete before the function in which they were dispatched returns. That's far from covering every use case of concurrency! A lot of the value of concurrency is in background operations. These simply can't be tied to the duration of a function call on the dispatch thread. Doing…

> A lot of the value of concurrency is in background operations. These simply can't be tied to the duration of a function call on the dispatch thread. If your background operation has those characteristics, maybe it is better to spawn a new process for it. Why a thread?

A process has much higher overhead (both in time and space), more complex communications and is not always possible in the first place. It also adds complexity to the build pipeline. These quickly adds up to being not worth it over a task or thread.

You wouldn't spawn a process for the rendering/input/physics/network/job/loader threads of a game engine for one. You can't spawn processes from a web app either.

Threads are actually pretty darn simple when you have either immutable data or uniqueness constraints. Deterministic parallelism is also very powerful while preventing all sorts of nasty bugs.

Re: Notes on structured concurrency, or: Go statement considered harmful

#53
post #48
post #31

Earlier quoted context omitted.

In Go you can use waitGroups to ensure that the goroutines are completed, similar to join. You’ll have to pass a cancel chan or context if you’d like to cancel/timeout.

With that approach, you increase the number of threads being used for a single "block" by how deep your "wait" stuff goes. In C# and Python, an async context shares a single thread loop that all joins jump back to. Simply starting a thread and waiting on it isn't suitable in most scenarios, namely web and UI dev. I wish Go had something like this as well.

In Go it doesn't really matter how many "threads" you have because Go has its lightweight threading model (aka coroutines) - creating a new "thread" is very cheap.

The reason C#, Python (and Rust), have that model is because they don't have coroutines and starting another thread is very expensive.

Re: Notes on structured concurrency, or: Go statement considered harmful

#54
post #32

This is very very long to explain a very simple pair of ideas: 1) You should be able to declare scoped blocks that mandate execution of all tasks started in that block ends when the scope ends. 2) This is fundamentally superior to all other forms of concurrency. I get it; this is basically what async/await gets you, but conceptually you can spawn parallel tasks inside an awaited block, and know, absolutely that all o…

+1. This is structured programming, applied to concurrent setting. The argument for structured programming was made and won in the '60s [0]. It is amazing that we keep making the same mistakes over and over again. > The unbridled use of the go to statement has an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. Usually, people take…

> The argument for structured programming was made and won in the '60s

Most code out there is fine using break and continue in loops, and early returns are pretty popular. All of these are unstructured programming (by the 60s definition). I don't think it's all that clear that it has won.

Re: Notes on structured concurrency, or: Go statement considered harmful

#55

This is usually why I end up using https://godoc.org/golang.org/x/sync/errgroup instead of straight go statements, as it addresses some of the cancellation and error propogation issues. When I think of my use of naked go statements, it's usually for periodic tasks; having something similarly structured for them would be a clear win to me (though potentially the impact is less significant, as it's less painful to writ…

Be advised that errgroup doesn't recover from panics to prevent them from killing the process.

Re: Notes on structured concurrency, or: Go statement considered harmful

#56
A point I believe the author has missed: Goroutines aren't simply forked functions, but are abstracted as independent, autonomous processes. Now, that doesn't mean 'go' is a sufficient tool to reason about goroutines, but this may also not be as useful a solution as he touts it to be.

Re: Notes on structured concurrency, or: Go statement considered harmful

#57
> As a result, every mainstream concurrency framework I know of simply gives up. If an error occurs in a background task, and you don't handle it manually, then the runtime just... drops it on the floor and crosses its fingers that it wasn't too important.

You ought to look into Erlang and Elixir on the BEAM vm/runtime. It's arguably the best example of this kind of concurrency (greenthreading, async) done properly with regards to error handling.

I don't write Elixir or Erlang, but I believe this process is managed by the supervisor. You can select various behaviours for when a process crashes or errors out[1]. For instance, you can have a process simply restart after it crashes. Combined with a fail-fast mentality, this produces remarkably fault tolerant and long lived applications.

[1]: http://erlang.org/doc/design_principles/sup_princ.html

Re: Notes on structured concurrency, or: Go statement considered harmful

#58
post #48

Earlier quoted context omitted.

With that approach, you increase the number of threads being used for a single "block" by how deep your "wait" stuff goes. In C# and Python, an async context shares a single thread loop that all joins jump back to. Simply starting a thread and waiting on it isn't suitable in most scenarios, namely web and UI dev. I wish Go had something like this as well.

In Go it doesn't really matter how many "threads" you have because Go has its lightweight threading model (aka coroutines) - creating a new "thread" is very cheap. The reason C#, Python (and Rust), have that model is because they don't have coroutines and starting another thread is very expensive.

The built in thread pools must have a limit.

Also, this isn't just a matter of keeping thread resources down, it's supporting scenarios like developing web and UI software.

For example, if I have "OnButtonClick" and it does async work, I'd like to also call other functions that may also touch UI components. WPF has support for binding async actions to UI events, and it eliminates the need to ever wonder what thread you are on.

In short, you can't "join" a thread on the UI thread. Proper async/await with a task scheduler is invaluable.

Also, C# has thread pools. Most people don't create a raw thread using "new Thread()". They would normally use "Task.Factory" or "Task.Run". If it is a long running thread, you can specify to Task.Factory that is it long running and it will remove your thread from the thread pool and put another one in it's place.

Re: Notes on structured concurrency, or: Go statement considered harmful

#59
post #41

This is very very long to explain a very simple pair of ideas: 1) You should be able to declare scoped blocks that mandate execution of all tasks started in that block ends when the scope ends. 2) This is fundamentally superior to all other forms of concurrency. I get it; this is basically what async/await gets you, but conceptually you can spawn parallel tasks inside an awaited block, and know, absolutely that all o…

The article makes a strong case. Even if rust allows formal verification, not all programmers use it, and if you use their library, you don't know if it's been verified. Take it back to the goto analogy: can you formally verify goto? Yes. Does that mean it's good to include as a language primitive? No. You are basically taking an entire argument, ignoring its merits, and saying "but you can do it another way". You ar…

It's not only that Rust allows formal verification, it's that it does it by default, writing unsafe Rust code for no serious reason is fundamentally frowned upon, and even then there is a serious effort to bring some tooling to bring more confidence even on "unsafe" Rust code.

> But I suspect the truth is that "njs was right", and I hope the it's sooner rather than later.

I'm not sure. The problem can and has been solved without the manually "pass the nursery object around" ""escape hatch"" for the general case of threads (because no: having the execution of spawning functions delayed by the lifetime of thread is arguably reasonable is some cases, but certainly not the general case of what threads are useful and used for)

It is still useful for tons of existing languages as a pattern anyway, but only if the use cases are suitable.

But the author is focused on a narrow use case of threads (and a narrow subset of the problems they introduce), and present their solution as a general truth and new fundamental control structure of computing, independent of already existing, in production, and arguably better solutions; and independent of analyzing the new problems their silver bullet introduces.

Re: Notes on structured concurrency, or: Go statement considered harmful

#60

Earlier quoted context omitted.

> A lot of the value of concurrency is in background operations. These simply can't be tied to the duration of a function call on the dispatch thread. If your background operation has those characteristics, maybe it is better to spawn a new process for it. Why a thread?

A process has much higher overhead (both in time and space), more complex communications and is not always possible in the first place. It also adds complexity to the build pipeline. These quickly adds up to being not worth it over a task or thread. You wouldn't spawn a process for the rendering/input/physics/network/job/loader threads of a game engine for one. You can't spawn processes from a web app either. Threads…

> A process has much higher overhead (both in time and space), more complex communications and is not always possible in the first place.

This can change if OSes start optimizing for the proposed convention.

> It also adds complexity to the build pipeline.

At least in C/C++ it doesn't. But again, this is also easily solvable if we want to.

> You wouldn't spawn a process for the rendering/input/physics/network/job/loader threads of a game engine for one.

Why not? In my mind, if you need 2-way communication between 2 threads, they should be siblings. If they need one way communication, there should be a parent-child structure. If they need no/minimal communication, they are better off as processes. I don't know which categories each of the threads you named fall into. I realize that this approach will require extensive redesign of existing software, very much like the elimination of goto required.

> You can't spawn processes from a web app either.

No reason for it to stay that way.

> Threads are actually pretty darn simple when you have either immutable data or uniqueness constraints. Deterministic parallelism is also very powerful while preventing all sorts of nasty bugs.

I won't pretend to know all those words :P I just think the article's proposal has some merit and we should consider it. >

Post reply on HN