Live data from Hacker News

Notes on structured concurrency, or: Go statement considered harmful

vorpus.org

221–230 of 234 posts

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

#221
post #83

Earlier quoted context omitted.

Huh? I thought "select" was "ALT"??

'go' is PAR with different syntax (Everything after the 'go' line is implicitly one branch of the PAR) 'select' is ALT

> Everything after the 'go' line is implicitly one branch of the PAR

No, it isn't, because Go doesn't do the italicized part:

"Everything in a PAR block will run in parallel and all their outputs will be collected and fed as the input to the next SEQ."

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

#222

Earlier quoted context omitted.

> I must disagree that asynchronous programming increase the complexity of writing good code Could you say more about why? It seems to me that purely synchronous code is much easier to reason about than async code, in that a) less is happening at once, and b) things happen linearly. That seems less complex almost by definition. I'd certainly believe that things like goroutines are better than the threading that came…

> But I can't see how it's less complex than linear code. I am being misinterpreted here (in a peculiar way that has happened before on HN...). I wrote that it does not increase the complexity "in any measurable fashion". It seems that you interpreted this as "concurrency is less complex than non-concurrency", when in reality I am saying "concurrency is a tiny bit more complex than non-concurrency" (extra emphasis on…

Sorry, but I'm just not seeing it.

Having a magic, behind-the-scenes event engine with a queue containing an unknown number of items executing in unpredictable order seems definitionally way more complex than not having any of those things and running the code in strict linear order. Performance is way better, sure, but at a cost of complexity and cognitive load.

Sure, the difference is tiny if everything works smoothly. But if everything worked smoothly, we wouldn't have jobs.

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

#223

Earlier quoted context omitted.

You realise that makes you sound exactly like an old Fortran programmer refusing to give up goto? A for loop is just a wrapped goto! ;-) I think the author proposes a useful way to structure multi-threading. The comparison to goto isn't perfect and he needs to play fast and loose with some terms to keep the analogy working but he makes a good point. I'm not convinced yet that the nursery pattern should be the only al…

As someone whose work includes hardware driver programming, I'd rather not give up goto. It would make a lot of error handling extremely cumbersome, and much less readable. :( However, I find "go" and "goto" to not intersect at all. I've written this many times in other comments on this thread, so I'd rather not type it out again, but the TL;DR: is that "goto" can make understanding a function when read difficult, wh…

Interesting; I program drivers but avoid goto entirely:

  NTSTATUS err = S_OK;
  if(!FAILED(err)) err = Op1();
  if(!FAILED(err)) err = Op2();
  ...
  if(FAILED(err)) /*cleanup*/

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

#224

Earlier quoted context omitted.

I think threads is a big enough of a problem that it can be divided into multiple sub problems where each individual problem deserves a solution. Yes, the resource sharing problem is harder than the part that this solves, but does that really matter?

Yes, because control flow and shared data locking work together. Or should.

But isn't it the case that while nurseries may not solve shared data handling, it certainly doesn't make it harder to reason about. If you get a fix rule that when the program has passed a certain point (the nursery) then the threads are done and the shared data related to the task will not be shared anymore.

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

#225

Earlier quoted context omitted.

Sure, but Erlang solves this problem indirectly. There's no risk of concurrent processes unexpectedly changing state (the real problem Trio addresses, or at least reduces the blast radius of) because there is no shared state.

> Sure, but Erlang solves this problem indirectly. It solves the issue quite directly. > There's no risk of concurrent processes unexpectedly changing state (the real problem Trio addresses, or at least reduces the blast radius of) because there is no shared state. That doesn't seem to have any relevance to Trio's purpose, a low-level spawn has this exact same guarantee and yet is pointed to as a problematic primitiv…

The entire reason why trio is proposed as useful is because of bad control flow leading to unpredictable state.

That's what was discussed in the example about file handles. The problem isn't bad concurrent control flow itself, the problem is that bad concurrent control flow enables unpredictable (or difficult to predict) changes in the apparent expected state all over your program. That's the same situation as with 'goto': spaghetti jumps are bad largely because they make it hard to think about "what is true when the flow of execution gets here here" at a given point in the program.

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

#226

Earlier quoted context omitted.

I guess we can debate what "hard" means, but I think "adds a lot more problems that are much harder to reason about and avoid" is exactly the definition of hard. If it's not, I'm honestly not sure what is. Torn reads, deadlocks, concurrent modification, the effects on optimizers, the overhead of scheduling and lock management. Each of these are deep, complicated issues you only have to deal with when writing concurre…

All the problems you present go into the category of sharing state. This is the unique problem presented by concurrent programming, which is no harder than all the other problems in programming. This does not mean that concurrent programming is hard , but that just like all other programming , it has some sharp edges. The issues related to sharing state can be easily avoided by, for example, using a CSP-style paradig…

> ...which is no harder than all the other problems in programming.

Well but for almost all of those problems you have something to help you. Yes manual memory management is hard, thanks GC/lifetimes. Yes error handling is hard, thanks exceptions (kind of). The point of OP is that practically all of the things we've built to help us with parallel/concurrent programming are still very low level, and your program still has to deal with the fallout in a way that it doesn't with GC or exceptions or other helpful abstractions.

> The issues related to sharing state can be easily avoided by, for example, using a CSP-style paradigm (channels in Go). In Go, this only leaves behind a deadlock, which are automatically panic, making it incredibly easy to debug.

There are only a few languages/platforms where CSP is feasible, and very few (none?) of them achieve the performance of CSP in Go because they don't/can't use segmented stacks. And even when using CSP you still need synchronization primitives. Parallel Go code uses mutexes everywhere. CSP in Go helps with _implementing_ parallel programs, but the Go team provides a lot of extra tooling to help _debug_ parallel programs that's well outside the definition of CSP --> the deadlock panic you cite is a good example actually.

> Overhead of scheduling and effects on optimizers are not related to concurrency/parallelism.

I disagree; mostly my evidence is "just google for volatile in C".

~~~

In general I still think parallel program is in a separate class of problems, but even if we stipulate it's just as hard as other problems, I still think we have much better tools for dealing with the other problems. I think we're getting there -- and CSP is part of that -- but it's definitely not the case that we've settled on a solution for 99% of problems.

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

#227

Earlier quoted context omitted.

All the problems you present go into the category of sharing state. This is the unique problem presented by concurrent programming, which is no harder than all the other problems in programming. This does not mean that concurrent programming is hard , but that just like all other programming , it has some sharp edges. The issues related to sharing state can be easily avoided by, for example, using a CSP-style paradig…

> ...which is no harder than all the other problems in programming. Well but for almost all of those problems you have something to help you. Yes manual memory management is hard, thanks GC/lifetimes. Yes error handling is hard, thanks exceptions (kind of). The point of OP is that practically all of the things we've built to help us with parallel/concurrent programming are still very low level, and your program still…

> ... and your program still has to deal with the fallout in a way that it doesn't with GC or exceptions or other helpful abstractions.

[citation needed]. What in the world is this "fallout"?

You can of course chose not to use CSP or some other abstraction, but that would be like chosing to do manual memory management (which you can also do in Go through cgo if you'd like), and the "fallout" is identical: You are on your own. CSP is of course not magic, but neither is a GC or lifetimes. You must always know the tools you are working with, as they have their own issues that must be dealt with.

If we look at the concerns in the article, Rust's lifetime actually remove those issues entirely, much in line with its "fearless concurrency" motto. Go's CSP is opt-in-although-aggressively-recommend, and is by no means a low-level construct.

A compare-and-swap is a "low-level" construct. You can of course pull in atomic primitives if you feel like doing so, but then you are choosing to go low-level.

Also, exceptions are a terrible, terrible thing. They make error handling much, much worse.

> There are only a few languages/platforms where CSP is feasible, and very few (none?) of them achieve the performance of CSP in Go because they don't/can't use segmented stacks

I do not see how this claim makes sense. Segmented stacks are an unnecessary green thread implementation detail that can both harm and benefit performance (if you use FFI, they harm performance a lot), and is neither necessary for green threads, nor related to CSP at all.

CSP works just fine in other languages. You can fully execute a CSP paradigm in C, just like you can avoid it altogether in Go and Rust.

Although, while you can avoid CSP in Rust, you still can't create the issues presented in the articles due to lifetimes. You would have to quite explicitly and intentionally get your hands dirty with unsafe code in order to shoot yourself in the foot here.

> I disagree; mostly my evidence is "just google for volatile in C".

"volatile" has nothing to do with anything here. Not only that, it is an entirely useless construct, as it does not provide reordering guarantees (that is, while subsequent reads see a previous write, the reads and writes may be reordered so that the reads now happen before the write). Proper constructs use memory barriers.

However, neither of these constructs have bad effects on optimization, unless you are aiming for code that executes fast without working at all. Also, memory barriers are low-level primitives. Unless you are designing synchronization primitives, you shouldn't touch them.

"volatile" and memory barriers have no effect on process scheduling at all. It does, however, necessarily affect the CPU instruction pipeline, but once you get your hands this dirty, you're way out of the scope of high-level programming safety net. Down here, we work with assembly.

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

#228

Earlier quoted context omitted.

How so?

Because the author engages directly with the core point Dijkstra made, and proposes a new abstraction which parallels with that core point. The author does so in a clear and thoughtful way which cannot be dismissed out-of-hand.

And yet misses the point entirely, presenting non-original work that only acts as a band-aid on a symptom, rather than dealing with the cause (which is not related to concurrency constructs at all).

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

#229

Earlier quoted context omitted.

> But I can't see how it's less complex than linear code. I am being misinterpreted here (in a peculiar way that has happened before on HN...). I wrote that it does not increase the complexity "in any measurable fashion". It seems that you interpreted this as "concurrency is less complex than non-concurrency", when in reality I am saying "concurrency is a tiny bit more complex than non-concurrency" (extra emphasis on…

Sorry, but I'm just not seeing it. Having a magic, behind-the-scenes event engine with a queue containing an unknown number of items executing in unpredictable order seems definitionally way more complex than not having any of those things and running the code in strict linear order. Performance is way better, sure, but at a cost of complexity and cognitive load. Sure, the difference is tiny if everything works smoot…

This is a bogus argument: "abstractions are complexities and lack control".

This argument can be equally applied to any other abstraction or convenience functionality, such as garbage collectors (unpredictable destructor execution, resource consumption and performance), OS schedulers (unknown number of items executing fighting for time quanta, unpredictable performance, unpredictable latency), high-level languages (unpredictable code generation, lack of ability to express proper intention related to processor abilities), etc.

However, the problem here is that your expectation of the abstraction is wrong, not that they introduce complexities. Furthermore, all abstractions give up control to the abstractions so that you do not have to concern yourself with it. You cannot both have full control over functionality and not concern yourself with the functionality.

An even engine is a scheduler like the OS scheduler. It is designed to take care of scheduling, making it not a concern for you, hiding the implementation (effectively making it "unpredictable"). If you want control, you have to give up the abstraction and go straight on the iron, just like you would have if you want precise memory behavior.

However, considering this a "complexity" would be wrong. There is nothing complex about asynchronous programming's lack of predictable execution order. The contract is that the code will execute when the event arrives, nothing else. This is by no means a complexity. In many engines, the execution of your code isn't even concurrent, only having I/O run in the background.

(I am intentionally ignoring buggy event engines, just like we ignore buggy GC's, kernels, compilers, CPU's, etc.)

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

#230

Earlier quoted context omitted.

As someone whose work includes hardware driver programming, I'd rather not give up goto. It would make a lot of error handling extremely cumbersome, and much less readable. :( However, I find "go" and "goto" to not intersect at all. I've written this many times in other comments on this thread, so I'd rather not type it out again, but the TL;DR: is that "goto" can make understanding a function when read difficult, wh…

Interesting; I program drivers but avoid goto entirely: NTSTATUS err = S_OK; if(!FAILED(err)) err = Op1(); if(!FAILED(err)) err = Op2(); ... if(FAILED(err)) /*cleanup*/

That would work, but I'm not sure I find it that pretty. Plus, it assumes all "ops" have compatible error output (which is unfortunately rarely the case).

What I instead do is usually:

    int some_func() {
        int err = OK;
        if (some_op() != SOME_OK_CRITERIA) {
            err = SOME_OP_FAILED;
            goto error;
        }
        ....


    error:
         // do necessary cleanup

         return err;
    }
I'm also mostly responsible for Linux and BSD kernel mode driver, as well as our user-mode driver (most of our driver exist as a shared user-mode component)—I wipe the Windows kernel mode driver off on someone else. :)

I have no allergy to goto at all, as long as its not used in absurd fashions (like how it's used in a duffs device). If a goto seems easier/prettier, I use a goto.

Post reply on HN