Hacker News new | ask | show | jobs
by lpedrosa 1528 days ago
I don't get the author's conclusion section, especially "structured concurrency does not suffer the colored function problem".

Structured concurrency has nothing to do with function colouring. It is how you organise and spawn tasks, so that you can have certain guarantees e.g. the group responds to cancellation as you'd expect, etc.

I don't see how async/await plays a part in this. You could have structured concurrency with a os threading model, as opposed to a userland threading model.

You can argue that the ergonomics of Zig's async story needs better documentation, especially if you're playing with function pointers. But please don't mislead readers that structured concurrency solves this.

2 comments

Author here.

> But please don't mislead readers that structured concurrency solves this.

It absolutely does. The reason is that, in structured concurrency, when a function spawns a thread, you can expect that that thread is joined before the function returns.

For callers, this means that they don't have to worry about whether the function they are calling spawns threads or not; they just call the function, and the function does its thing.

It is the same principle as standard control flow constructs: they use `goto` underneath, but you don't need to worry about the use of `goto` because they guarantee that execution will always end up at the same place afterward (barring early returns and things like that).

So when I say that structured concurrency does not suffer from the colored function problem, what I am saying is that callers, and programmers, do not need to care what functions do underneath when they are called.

Also, structured concurrency solves the function coloring problem because you can use any function, whether it spawns threads or not, as first-class functions, or function pointers, and it all works without needing any special code, or work, or compiler trickery.

> So when I say that structured concurrency does not suffer from the colored function problem, what I am saying is that callers, and programmers, do not need to care what functions do underneath when they are called

Thanks for clarifying what you meant with your original comment in the article. I completely agree that this is a very big advantage, as opposed to not using structured concurrency.

However, it seems that you are conflating two things with this assertion. For example you say:

> The reason is that, in structured concurrency, when a function spawns a thread, you can expect that that thread is joined before the function returns

While structured concurrency gives you this guarantee, this is orthogonal to whether a function the thread runs is "red" or "blue".

"Red" functions exist in a language because you want blocking to happen on userland so it's cheap to unschedule the task that is blocking.

Structured concurrency doesn't remove "red" functions from a language. They do make the ergonomics of using such functions much easier on the caller e.g. python's trio library [0].

To actually "remove Red functions from the language" you can make the user think they're just using plain "blue" function techniques for blocking.

Go achieves this by only allowing use userland threading.

Java's project Loom [1] achieves this by making common suspension/yielding points (e.g. IO) compatible with both userland and OS threading.

To me, this is what solves the colouring problem i.e. you only have one colour and keep using and writing your code as you always have been.

Structured concurrency, much like you said, is the added bonus that helps reasoning about the lifecycle of tasks a function may or may not spawn.

[0] - https://trio.readthedocs.io/en/stable/index.html

[1] - https://wiki.openjdk.java.net/display/loom/Main

(edit note: my phone messed up quoting)

I was going to say that this isn't quite right, that you could implement structured concurrency at the language level and have a compiler know how to thread the needle between the different "colors".

But then I realized this is exactly what Go does (with the scheduler intercepting system calls), just minus the structured concurrency point, strengthening your point that these are orthogonal.

Go solves the coloring issue without structured concurrency, that's true. But that statement says nothing about whether structured concurrency can or cannot solve the coloring issue, and if it could, then they're not orthogonal.

I think OP saying that structured concurrency solves the coloring problem is a very similar argument to the authors of Zig saying Zig is colorblind; it might be true for a portion of the userbase, mainly those who are callers, and mostly not for those who are library implementers.

I also think that the structured concurrency feature of "you can expect that the {thread, task, fiber, goroutine} is joned before the function returns" is weaker for the purpose of colorblindness than "you can expect that the {thread, task, fiber, goroutine} is joined before the next instruction is executed" which is what I think you'd need as a single tool for colorblindness

I do agree with a lot of what you said.

> While structured concurrency gives you this guarantee, this is orthogonal to whether a function the thread runs is "red" or "blue".

I disagree with this.

Making sure that threads join before return is a big reason why I can pass functions around easily as function pointers and not worry about it.

An example is in my up-and-coming build system. [1]

In that code, I have a function that opens a threadset (my term for trio's nurseries) and executes a build. Once my build system is out of alpha, the direct call to that function will be replaced by a function pointer to the build type of the user's choice. (For example, you might want a "quick build" to just rebuild a file saved by your editor, or a "full build" regardless of what is already built, or just a normal build.)

Despite using function pointers, I don't need to worry about whether threads are spawned or not. In Zig, you do need to worry about if a function is async or not when using function pointers.

This includes if I make a build type whose function will not spawn threads at all, by the way. (For example, if building on a platform where you don't have the memory to spare for extra threads.) I can use that build type's function as a function pointer the same way I would use any function that does use a threadset.

In other words, while Zig is colorblind at compile time, structured concurrency is colorless at both compile time and runtime.

[1]: https://git.yzena.com/Yzena/Yc/src/commit/a7f535f8d0df45120e...

Isn't structured conc. about using scopes (, syntax) to provide concurrency "control flow"?

If so, then it does solve the color problem. The alternative to structural (ie., scopy-syntaxy) flow primatives, is to reuse existing control flow primtives (ie., function calls) and just layer on coloring.

By declining to reuse functions this way, you are dropping the need for color.