News for hackers. Stuff that matters.
Re:Unravelling `Async for` Loops by daenz on Tuesday September 14, 2021 @09:55PM (#28534264)
In my opinion, async is such a mistake because it exposes a complicated abstraction to developers that could (and should!) be totally hidden by the runtime. Async "feels" cool and powerful because you get to learn about the intricacies of concurrency via coroutines, but at the end of the day, you just produce a tangled cobweb of difficult-to-follow code that is absolutely infested with explicit references to the concurrency. And it's so easy to get wrong.

Golang gets it right with goroutines. Languages should have extremely light and performant concurrency that efficiently map to OS threads. And the developer should only care about synchronization, not personally accounting for every possible context switch.

Re:Unravelling `Async for` Loops by jayd16 on Tuesday September 14, 2021 @10:15PM (#28534438)
Explicit yielding of execution is often very important, for example, UI code or any case where exact OS thread matters.
Re:Unravelling `Async for` Loops by geofft on Tuesday September 14, 2021 @10:07PM (#28534370)
The problem is that that completely hiding it in the runtime has to be baked into the language's design, or at the least you have to have designed your language in a way that makes it easy (Haskell is a good example here - because functions are pure, you don't have to worry about implicit concurrent modifications of the same data).

Python, as a language, has a lot of reliance on global context and a lot of dynamic functionality. Imports happen at runtime, not compile time. You can modify the attributes of a class. You can reassign a function. And so forth. You can do nonsense like this, where a isn't even defined until the second-to-last line:

    $ python3
    >>> def f():
    ...  yield 5
    ...  yield a
    ... 
    >>> x = f()
    >>> next(x)
    5
    >>> a = 10
    >>> next(x)
    10
All of this needs some answer when writing concurrent / parallel code; the answer of "Don't worry about it" just gets you racy and unreliable code. The answer of "Make it impossible" changes the language (which is a fine answer, but you're better off starting from scratch like Go than adapting Python). So the remaining answer is "Make the programmer explicitly acknowledge concurrency."

https://glyph.twistedmatrix.com/2014/02/unyielding.html (which predates Python having proper async-await support by a few years, and references the "Tulip" project that became asyncio) makes a good argument that "Don't worry about it" doesn't work and "Make the programmer explicitly acknowledge concurrency" is fine in practice.

Anecdotally, my (limited) experience writing concurrent Python code in Trio has required no learning of the intricacies of coroutines and the implementation and also relatively easy-to-follow code, but yes, with explicit references to concurrency. Also, anecdotally, since my performance problem is just "I would like to parallelize multiple HTTP requests" and not "I am an OS scheduler and need to optimize every cycle" (in which case I wouldn't be writing Python), I'm happier writing code that accounts for context switches but in turn never has to care about locking than trying to properly manage locks and lock ordering and think about types of locks and all that.

Re:Unravelling `Async for` Loops by daenz on Tuesday September 14, 2021 @10:42PM (#28534597)
I mostly don't disagree with you. The only point I really disagree on is that Python should have stuck with their limited threads instead of enabling this kind of "safe" concurrency. I think they are letting a genie out of the bottle with async, and it's going to result in making it very easy for people who don't understand what they're doing to write even more complex code that nobody wants to touch because they don't understand it either.

Yes you can do very magical things with Python, and no they're not very safe, but at least that level of magic requires some level of skill, and the assumption that with that skill comes a responsibility for putting up warning signs and providing guidance. But most of the time, people should not be doing magic, and if they are, it should be hidden so completely, that it appears no magic is actually happening.

Anecdotally, my experience with working with engineers who are trying to understand new async code, has been dismal. I used to think "oh, these engineers just don't want to put in the work to understand what is happening in the codebase." But it's actually hard to explain what is happening to other engineers who have not been "initiated" in async. So they end up writing broken solutions and then feeling frustrated, because there's a resistance to the nature of the abstraction. Now you could argue that they should just hunker down and learn more, but personally I think if new abstractions do not come with a natural intuition, then they are not good abstractions.

Re:Unravelling `Async for` Loops by pmoleri on Wednesday September 15, 2021 @07:32AM (#28537766)
IMO it's a beautiful abstraction. You don't need to think about threads, just that your code will continue later when the result is available. In threaded environments like .net you can also create a CPU bound task that will be run in the background, yes in a thread pool, but you don't really need to know the specifics, it's something that you create, release the current thread (e.g. UI thread). For some IO tasks there's no even a waiting thread, so in that case thread would be the wrong abstraction.
Re:Unravelling `Async for` Loops by speedgoose on Wednesday September 15, 2021 @12:37AM (#28535310)
I personally prefer the async model over synchronization because for me, it's a lot simpler to manage the async/await than having to synchronize. I find golang to be more difficult than javascript in this aspect. Golang has better concurrency though.
Re:Unravelling `Async for` Loops by vbezhenar on Wednesday September 15, 2021 @01:31AM (#28535584)
If you don't want to synchronize, you need to run your app on a single OS thread. That's the case for node.js. You can configure golang runtime to run on a single thread as well, I suppose that it would remove any need of synchronization either.

There are plenty of languages with both async model and multicore runtime support. For example Kotlin on JVM. You still need to synchronize on shared resources (or use other patterns like message passing).

Async does not magically save you from synchronization. It's just a quirk of node.js implementation.

Re:Unravelling `Async for` Loops by speedgoose on Wednesday September 15, 2021 @03:41AM (#28536362)
NodeJS has more than one single thread, network IOs notably are on another thread. That's the beauty of it.
Re:Unravelling `Async for` Loops by patrickthebold on Wednesday September 15, 2021 @07:36AM (#28537794)
Why would network IO be on another thread? libuv gives node non blocking network IO (or so was my understanding).
Re:Unravelling `Async for` Loops by zbentley on Thursday September 16, 2021 @08:25AM (#28550831)
Unsure about network (if it's true that it's on a separate thread, perhaps it's because network operations take cpu time even when readied asynchronously--e.g. even after you select/[e]poll to determine what events to trigger next, the actual read/write/sendfile/whatever ops do take a little time). However, filesystem IO is performed in threads in libuv on many platforms, due to the unavailability of stable async IO capabilities.
Re:Unravelling `Async for` Loops by pjc50 on Wednesday September 15, 2021 @07:01AM (#28537575)
> not personally accounting for every possible context switch.

Context switches remain expensive.

I'm kind of on the fence about coroutines. They can sometimes simplify writing performant IO-dependent code, or can make it harder to debug. It feels that they've not reached their "final form", nor has the right set of structured programming norms developed around them yet.

Re:Unravelling `Async for` Loops by truffdog on Wednesday September 15, 2021 @01:52PM (#28542197)
I think the problem is they are really good for writing proxies like nginx, linkerd, haproxy, that everyone uses, so you have these really high profile, high performance siren songs. But they have a crucial design advantage- it is a tight loop making a small number of decisions before punting the work off to the OS.

Then people start trying to apply coroutines to a bunch of arbitrarily complicated and unbounded business logic things and everything blows up.

Re:Unravelling `Async for` Loops by fsociety on Wednesday September 15, 2021 @02:20AM (#28535847)
Goroutines make sense for Go, but it is a bit much to say goroutines > async. Nothing is free and in this case FFI hurts and boy does it hurt in Golang. Python strives off of C code.
Re:Unravelling `Async for` Loops by laerus on Wednesday September 15, 2021 @01:54AM (#28535724)
> not personally accounting for every possible context switch

In Python land explicit is better than implicit as this is cooperative multitasking after all.

It's also worth reading through https://vorpus.org/blog/notes-on-structured-concurrency-or-g...