Live data from Hacker News

What async promised and what it delivered

causality.blog

281–290 of 317 posts

Re: What async promised and what it delivered

#281
post #232

Earlier quoted context omitted.

Of course I'm comparing them. Threading and async are two solutions to the same problem: How do you write high performance event driven systems like network services? How do you solve the C10K problem (or more recently the C10M problem)? If you use a thread per connection (or green threads like Go), you don't also need async. If you have async (eg nodejs), you can get great performance without threads. You're right t…

You wrote: > The job wouldn’t have been done. They would have needed threads. And mutexes. And spin locks. And atomics. And semaphores. And message queues. And - in my opinion - the result would have been a much worse language. My point is that you do need mutexes, spin locks, etc with async as well, given that you have a multi threaded platform. So no, we have basically 2x2 stuff we are talking about with very diffe…

> My point is that you do need mutexes, spin locks, etc with async as well, given that you have a multi threaded platform.

No, Javascript isn't a shared-memory multi threaded platform. It would only need mutexes and so on if we added threads to javascript, as the comment I was replying to suggested should have happened:

> At any step in that sequence, the language could have introduced green threads and the job would have been done

Re: What async promised and what it delivered

#282

Earlier quoted context omitted.

> In my experience people complain about it because they are coming from a blocking first mindset. They're trying to shoehorn async calls into an inherently synchronous structure. There's no "inherently synchronous structure", at least not in Javascript. The nature is synchronous, asynchronous is an illusion built on top of it. Which is why you can easily block an "asynchronous" program: while (true) {} on any async…

You're blending concepts. All parallelism is asynchronous, but not all "asynchrony" is parallel. I have to use Python as as an example since I don't have much experience with JavaScript, but when you're using asyncio, that's single threaded non-blocking IO (asynchronous). Each use of "await" yields execution back to the event loop, and it can can schedule some other task to run. Tasks can run asynchronously, but no i…

>All parallelism is asynchronous, but not all "asynchrony" is parallel.

Sure, but my comment was not about parallelism compared to asynchronous, but about the idea that Javascript is above the "blocking first mindset".

Javascript depends on a blocking (single-threaded, run-to-completion) backend. The asynchronicity on top of this blocking layer is an abstraction based on cooperative yielding.

Whereas e.g. Erlang's asynchronicity is part of the runtime model itself.

Re: What async promised and what it delivered

#283
post #90

Async is a Javascript hack that inexplicably got ported to other languages that didn't need it. The issue arose because Javascript didn't have threads, and processing events from the DOM is naturally event driven. To be fair, it's a rare person who can deal with the concurrency issues threads introduce, but the separate stacks threads provide a huge boon. They allow you to turn event driven code into sequential code.…

> And as for Rust - that's beyond inexplicable. No, you appear to have no idea what you're talking about here. Rust abandoned green threads for good reason, and no, the problems were not minor but fundamental, and had to do with C interoperability, which Go sacrifices upon the altar (which is a fine choice to make in the context of Go, but not in the context of Rust). And no, Rust does not today have a green thread i…

> the problems were not minor but fundamental, and had to do with C interoperability,

The interoperability problems came from a design choice they made: they wanted to invisibly swap between OS threads if a blocking call was made. That was the wrong design choice for Rust. It is not something green threads require - it's an additional burden they imposed on themselves.

> a fine choice to make in the context of Go,

Correct. It was.

> enable concurrency even on systems where threads do not exist,

And that was their mistake. Green threads are just user space stacks, which any CPU that has a stack pointer can support. They don't need OS threads. But in the initial Rust implementation they added "invisibly supports blocking calls" implementation. That does indeed need full OS thread support, and breaks the C interface.

They could have just abandoned that mistake, implemented pure green threads and they would have been done. But no, they instead they implemented async. So instead we got new keywords, had to wait years for various new language features to stabilise (like pin) and lifetime 'static. I'm sure the language designers spent many happy man months solving the problem "How do we represent a suspended function as a type-safe State Machine?".

What we got for all that effort is something so famously difficult to use, even experienced Rust programmer shy away from async. Whereas with green threads a programmer could just reuse their knowledge and mechanisms Rust has for sharing data across multiple stacks, for async they have now to battle the borrow checker on a new front - the language to absorb their function state into 'static area that has fewer lifetime guarantees. The alternate green thread implementation could have had just two colours - blocking and non-blocking, now we have a colour for every async library. Whereas with green threads they could have stabilised the interface to underlying the event loop, we are now stuck with every library writer having their I/O in their own mini API, which they swap for each async implementation.

And while the type-safe State Machine they did come up with is an engineering marvel, it's very complex and requires data copies under the hood. Green threads just reuse an existing mechanism for saving a function's state - the CPU's IP Address, the registers and the stack. It's so simple anyone can understand it, it isn't something additional to learn because the program's main already uses it, it is very, very fast because it's undergone decades of refinement, and it's also type safe!

But instead in what we got the syntax is a mess, the error messages are incomprehensible, and we re-implemented the CPU's stack management in software (invariably slower) because they didn't want to write the logic to grow a hardware stack.

The language will be better off recognising it was all a huge mistake, and moving towards standardising on a green thread implementation like mioco. Everyone who has to manage 100,000 lightweight processes would be immensely grateful for the simpler, faster API. Some of the truly impressive stuff done for async, like knowing how much stack some function calls can take, would be really useful for green threads. You know the size of the stack you need - to the byte! So it's not a complete loss.

Re: What async promised and what it delivered

#284
post #117

Earlier quoted context omitted.

Once you write enough code, you'll realize you need synchronization primitives for async code as well. In pretty much the same cases as threaded code. You can't always choose to write straight code. What you're trying to do may require IO, and then that introduces concurrency, and the need for mutual exclusion or notification. Examples: If there's a read-through cache, the cache needs some sort of lock inside of it.…

> Once you write enough code, you'll realize you need synchronization primitives for async code as well. In pretty much the same cases as threaded code. I've been programming for 30 years, including over a decade in JS. You need sync primitives in JS sometimes, but they're trivial to write in javascript because the code is run single threaded and there's no preemption. > What you're trying to do may require IO Its us…

Ok, you've been programming for years. But didn't learn a lot about threads, apparently.

> Multithreaded code is often much harder to reason about than async code, because threads can interleave executions and threads can be preempted anywhere.

No, green threads / fibres or whatever you want to call them explicitly don't interleave executions. They are a form of cooperative multitasking. Async/await is another form of co-operative multitasking. One former just builds on what we already have. The latter re-invents the universe.

By the by, the blocker for Javascript green threads wasn't preemption, mostly because there isn't any. It's that Javascript has a "run to completion" model. If the DOM calls a javascript event (which is effectively how all javascript is invoked in a browser), it doesn't block, so it always runs to completion. Green threads break that model. It's not a insurmountable break - the DOM events could always still return immediately, but they could start a green thread that returns to them as soon as they block. Thinking about it, the change is possibly smaller than language changes required by async/await.

If you can reason about where an await is, you can reason about where a green thread yields. The only difference is that one of them clutters your syntax and the other doesn't.

Re: What async promised and what it delivered

#285
post #272

Earlier quoted context omitted.

> But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap. and async prevents this how?

my statement was in response to "fs.readSync shouldn't exist". that is how.

if it didn't exist, the async version would still exist, which you could use to get it done asap

Re: What async promised and what it delivered

#286
post #266

Earlier quoted context omitted.

I don't think those benches are much of a flex, even by the author's own description you'd be fine with any of them. They all have acceptable performance and don't show any order of magnitude differences or non-linear scaling problems. Further, the benches that are showing best there are non-thread-stealing scenarios, not tokio. I also suspect simply tuning the thread-based workloads more aggressively would have the…

You assumed > Likely more efficient than half the async runtimes out there. The benchmark shows the opposite: 2 (multithreaded async runtime) vs 7 (threads) * 10^8 ns per request for 2k requests/s. > non-linear scaling problems. oh, look closely, the relative gap increases with #requests/s

By TFA's own quote: "This means you probably shouldn’t put too much trust into the results I measured or base important decisions on the outcomes."

Your level of self-assured snark is way out of proportion to both the article and chart I'm looking at.

Props to the person who wrote the article. Your use of it however, is dubious.

Re: What async promised and what it delivered

#287

Earlier quoted context omitted.

> Operating systems can grow the amount of real memory allocated to a thread, but never shrink it. Operating systems can shrink the memory usage of a stack. madvise(page, size, MADV_DONTNEED); Leaves the memory mapping intact but the kernel frees underlying resources. Subsequent accesses get either new zero pages or the original file's pages. Linux also supports mremap, which is essentially a kernel version of reallo…

> C programs can't do it because pointers to stack allocated objects may exist. They sure shouldn't exist to the unused region of the stack though; if they do, that's a bug (because anything could claim that memory now). You should be free and clear to release stack pages past your current stack pointer.

There isn’t any operating system or compiler that does this today, and it probably isn’t worth it to pursue. Enlarging the stack via page fault is really expensive, so you would need really advanced heuristics to prevent repeatedly unmapping/remapping those pages.

The correct tool for myriad of small tasks is coroutines / green threads / async tasks, so why spend any energy optimizing threads for that purpose instead of what they are already good at?

Re: What async promised and what it delivered

#288

Earlier quoted context omitted.

Or use OCaml 5 which has a full algebraic effects system that solves the function coloring problem while still being highly performant.

How do they solve it?

I'm glad you asked: https://lukstafi.github.io/curious-ocaml/new_book.html#chapt...

Basically, any function can handle any effect, they don't need to be marked a special way like async await.

Re: What async promised and what it delivered

#289

Earlier quoted context omitted.

These things _are_ function colouring, but they show function colouring isn't scary or hard. The original function colouring essay was much more about JavaScript's implementation than a general statement. If JavaScript had exposed a way for a synchronous function to call back into the runtime to wait for an async function to complete, it would still be just as coloured, but no one would be complaining about colour (d…

I think this is right. More specifically, the problem is JavaScript function colors mean Sync/Async, whereas Zig's mean Non-IO/IO. Using function colors for async is fundamentally unnecessary, whereas for I/O it is fundamentally necessary. You should be able to define a synchronous function that calls an asynchronous function. But it makes no sense to define a non-IO function that calls a function that does IO. EDIT:…

Also with the exception of trace/debug print. At least I'd consider it a fair exception.

(it's what I'm planning for my own lang, having `trace` be "blessed" to not make a function unpure despite I/O)

Post reply on HN