Live data from Hacker News

Notes on structured concurrency, or: Go statement considered harmful

vorpus.org

211–220 of 234 posts

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

#211

Earlier quoted context omitted.

>and have not explicitly opted in to the scheme. I think you're overestimating nurseries here. They still don't handle the case you described of a deeply nested setTimeout: import asyncio import trio import threading async def my_tricky_function(): threading.Timer(5, lambda: print('first')).start() await trio.sleep(.5) async def a_function(): async with trio.open_nursery() as n: n.start_soon(my_tricky_function) print…

> they cannot and do not have. sigh~ In this implementation, or perhaps, in python at all... Does that make it completely valueless? I would venture to suggest that maybe there's a big wide world of languages that actually support controlling how threads are spawned, where it might not be. Maybe its worth considering.

Or perhaps at all.

I agree that it's worth considering other ways of handling concurrency. But we should do so by staying within the realm of reality.

Your understanding of nurseries, conceptually, does not match their capabilities. It's not about any language or implementation, it's that what you think they can do isn't possible. It violates the halting problem.

You can't statically infer whether or not a function will have async side effects without opting into some scheme that describes those effects. If you do that, nurseries provide some guarantees. But those same guarantees are provided by just using async/await, which tracks explicitly which functions are async and which are not.

Nurseries do potentially provide some advantages when dealing with tasks that you want to outlive their scope, which async/await doesn't handle well, and when dealing with an unbounded number of async calls (maybe, I think an `async for` construct handles it too).

But otherwise, most of the advantages you seem to think nurseries provide aren't. And not just by this implementation, but by any implementation. They are provably not providable by any implementation that isn't equivalent to marking your async functions as async.

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

#212
post #200

Earlier quoted context omitted.

The description of the nursery is you can pass it around wherever you like. More generally, this concept seems to have been designed in a language without lifetimes. You obviously can have a scoped threadpool without a 'static lifetime, but if you want to pass it around to arbitrary locations then you do.

I think the whole point of the nursery's design is that you can create and destroy them during the life of your program, and that destroying them forms a barrier where the owner of the nursery waits for its children to finish. Having a nursery with a static lifetime in Rust would therefore be pointless, as it would live until the end of the program.

I think you misunderstand. I'm talking about writing something like `ScopedThreadpool`, which means any external data referenced by the threadpool must have a static lifetime (or rather, it means the ScopedThreadpool cannot reference anything on the stack, because that would prevent you from e.g. returning it to your caller or passing it to another thread). The ScopedThreadpool itself can be created and destroyed whenever.

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

#213

Earlier quoted context omitted.

> I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you. Not at all. Concurrent/parallel code isn't particularly difficult to write—there's just a unique class of problems related to it that might occur, but that does not mean it is hard . Rather, the industry has through several decades demonstrated an inability to write bug free code in general. I w…

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 paradigm (channels in Go). In Go, this only leaves behind a deadlock, which are automatically panic, making it incredibly easy to debug.

Overhead of scheduling and effects on optimizers are not related to concurrency/parallelism. The scheduler is affected by many things even under single-threaded execution which is far beyond the scope of normal application developers, and optimizers are largely unaffected by concurrent/parallel programming (although shared structures must internally present memory barriers). The optimizer is also beyond the scope of normal application development.

If you are doing development that requires precise control over optimizations and scheduling (like I do), then all bets are off.

However, with "normal" (i.e. not-processing-8x100Gb/s streams) programming, concurrent programming is a breeze unless you intentionally shoot yourself in the foot.

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

#214

This addresses the wrong problem. The real issue is control over data shared between threads, not control flow. C/POSIX type threads have no language support for indicating what data is shared and which locks protect which data. That's a common cause of trouble. The big question in shared memory concurrency is "who locks what". Most of the bugs in concurrent programs come from ambiguities over that question. Early at…

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.

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

#215
post #196

Earlier quoted context omitted.

Can you force explicit nursery passing though? One might make a global nursery object and then just rely on that.

In some existing languages, yes. But it’s easy to imagine future languages (including future versions of current languages) that disallow creating nurseries outside of a function scope.

At this point you're pretty much reinventing Monads.

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

#216

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…

ErrGroup is nice, but it was created before contexts existed, and doesn't have support for cancellation. I have a bounded worker pool executor that handles cancellation that I'm currently extracting from a private project; shout out if interested.

The errgroup I linked has a single constructor `WithContext(ctx.Context) (*Group, context.Context)`. I think you might be thinking of the stdlib's sync.ErrGroup :)

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

#217

Earlier quoted context omitted.

Then what is the difference between the nursery escape and joining a thread or extracting the value of a promise? Both of these will bubble any error back into the dispatch thread. Seems like we're just layering indirections (I wouldn't call that an abstraction) for little added value over the existing constructs.

You might want to consider that this exact thread of questioning does not deviate at all from the similar line that was used to defend `goto` against structured programming. I’m not saying that you’re wrong or that this is necessarily the same thing, but the author addresses these kinds of questions through analogy to the `goto` debate in the article. It’s worth thinking about their similarity. TL;DR, this doesn’t ad…

I agree, but I still wouldn't call a nursery an abstraction; its an indirection, and a mutable one at that. I also disagree with the goto analogy; concurrency and control flow are two distinct things, they have much more differences than similarities.

Implementing a promise as a monad will yield all the same benefits while also keeping the ability to compose and be immutable; and then you have an abstraction and the result is simple.

I agree restrictions make the code better, this just isn't one of these cases to me.

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

#218
post #75

Earlier quoted context omitted.

I get compile-time warnings or runtime errors when I create unhandled promises. What more is needed?

Needed is a strong word, but what this provides are guarantees when reading the code. Much like you know that control flow will come back to the function you're reading after calling another function. We didn't need to know that control flow will resume in the calling function, but it turned out to be very useful.

What guarantees? Nothing prevents a function receiving a nursery from not using it. Unless the language can enforce it you don't really gain anything valuable over returning a Promise, except more complex code that doesn't compose.

> Much like you know that control flow will come back to the function you're reading after calling another function.

What about continuations? Exceptions? Aborts? setjmp()? I can think of many cases where control doesn't return to the caller that are perfectly valid.

Basically, either the code is so simple theres obviously no bugs, or the code is so complex theres no obvious bugs. I feel a nursery is closer to the later than the former.

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

#219

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…

> while “go” is clear when read… I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you. Languages like go (and Rust, to a greater degree) have improved the situation. But there are still a ton of sharp edges that existing approaches still have, and—often—they’re ones that aren’t readily apparent until after a project starts growing and begins unearthi…

> I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you.

But mostly so for the reasons nurseries try to solve (and actually manage to somehow solve some)? I'm not convinced.

Lifetime of threads is usually easy compared to races/deadlock/etc. on the resources they use.

Not to say we shall not use nurseries where applicable. But is it the new fundamental structure concurrency should be based on? Debatable.

My wild guess is that the Rust approach of concurrency (even if it is not exactly on the same subject, but we are trying to find the fundamental way of structuring things) will have more impact.

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

#220

Earlier quoted context omitted.

ErrGroup is nice, but it was created before contexts existed, and doesn't have support for cancellation. I have a bounded worker pool executor that handles cancellation that I'm currently extracting from a private project; shout out if interested.

The errgroup I linked has a single constructor `WithContext(ctx.Context) (*Group, context.Context)`. I think you might be thinking of the stdlib's sync.ErrGroup :)

Oops, I didn't realize that. Thanks. Mine implementation supports a bounded (max concurrency) mode, though!
Post reply on HN