Live data from Hacker News

What async promised and what it delivered

causality.blog

221–230 of 317 posts

Re: What async promised and what it delivered

#221
post #181

Earlier quoted context omitted.

'readSync' does two different things - tells the OS we want to read some data and then waits for the data to be ready. In a good API design, you should exposed functions that each do one thing and can easily be composed together. The 'readSync' function doesn't meet that requirement, so it's arguably not necessary - it would be better to expose two separate functions. This was not a big issue when computers only had…

> tells the OS we want to read some data and then waits for the data to be ready No, it tells the OS "schedule the current thread to wake up when the data read task is completed". Having to implement that with other OS primitives is a) complex and error-prone, and b) not atomic.

The application in question is frozen for that period though, that's the wait they're referring to.

Even websites had this problem with freezing the browser in the early AJAX days, when people would do a synchronous XMLHttpRequest without understanding it.

Re: What async promised and what it delivered

#222
post #143

Earlier quoted context omitted.

Data races are a specific race condition - they may be safe or cause tearing. Serially, completely synchronously overwriting values is none of these categories though.

You're mixing up quite a few somewhat related but different concepts: data races, race conditions, concurrency and parallelism. Concurrency is needed for race conditions, parallelism is needed for data races. Many single threaded runtimes including JS have concurrency, and hence the potential for race conditions, but don't have parallelism and hence no data races.

Concurrency with a single thread of execution runs with complete mutual exclusion, so no "pure" single threaded concurrency is definitely race condition free.

What we may argue over (and it becomes more of a what definition to use): IO/external event loop/signal handlers. These can cause race conditions even in a single threaded program, but one may argue (this is sort of where I am) that these are then not single threaded. The kernel IO operation is most definitely not running on the same thread of execution as JS.

I think I have been fairly consistent in the definition of a data race as a type of race condition, where a specific shared memory is written to while other(s) read it with no synchronization mechanism. This can be safe (most notably OpenJDK's implementation is tear-free, no primitive or reference pointer may ever be observed as a value not explicitly set by a writer), or unsafe (c/c++/rust with unsafe, surprisingly go) where you have tearing and e.g. a pointer data race can cause the pointer to appear as a value that was never set by anyone, causing a segfault or worse.

Re: What async promised and what it delivered

#223
post #87

> Language designers who studied the async/await experience in other ecosystems concluded that the costs of function coloring outweigh the benefits and chose different paths. Not really. The author provides Go as evidence, but Go's CSP-based approach far predates the popularity of async/await. Meanwhile, Zig's approach still has function coloring, it's just that one color is "I/O function" and the other is "non-I/O f…

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. A while back I just started leaning in. I write a lot of Python at work, and anytime I have to use a library that's relies on asyncio, I just write the entire damn app as an asynchronous one. Makes function coloring a non-issue. If I'm in a s…

> They're trying to shoehorn async calls into an inherently synchronous structure.

You can make any async system synchronous. It's much harder to mske a sync sydtem asynchronous. (Misquoting from something Erlang-related).

There are many cases when I don't care if a function call is asynchronous. I'm happy to wait for the result. Yet too many systems tell me I can't, for no good reason.

Re: What async promised and what it delivered

#224

Earlier quoted context omitted.

The amount of stack you pay for on a thread is proportional to the maximum depth that the stack ever reached on the thread. Operating systems can grow the amount of real memory allocated to a thread, but never shrink it. It’s a programming model that has some really risky drawbacks.

> 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…

Stack memory is never unmapped until the thread terminates as far as I know. I don’t know of any kernel that does this, for precisely the reason you arrive at by the very last sentence.

Re: What async promised and what it delivered

#225
post #117
post #98

Earlier quoted context omitted.

> At any step in that sequence, the language could have introduced green threads and the job would have been done. 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. Multithreaded code is often much harder to reason about than async code, because threads c…

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 usually possible to factor your code in a way that separates business logic and IO. Then you can make your business logic all completely synchronous.

Interleaving IO and logic is a code smell.

> The Promise static methods (any, all, race, etc) are particularly useful. But, you could implement that for threads. I believe that this convenience difference is more due to modernity, of the threading model being, what 40, 50, 60 years old, and given a clean-ish slate to build a new model, modern language designers did better.

Then why don't see any better designs amongst modern languages?

New languages have an opportunity to add newer, better threading primitives. Yet, its almost always the same stuff: Atomics, mutexes and semaphores. Even Rust uses the same primitives, just with a borrow checker this time. Arguably message passing (erlang, go) is better. But Go still has shared mutable memory and mutexes in its sync library.

> But it raises the idea: if we rethought OS-level preemptible concurrency today (don't call it threads!), could we modernize it and do better even than async?

I'd love to see some thought put into this. Threading doesn't seem like a winner to me.

Re: What async promised and what it delivered

#226
post #133
post #98

Earlier quoted context omitted.

> At any step in that sequence, the language could have introduced green threads and the job would have been done. 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. Multithreaded code is often much harder to reason about than async code, because threads c…

Now you are comparing single threaded code with multi threaded, which is a completely different axis to async vs sync. Just take a look at C#'s async, where you have both async and multi threading, with all the possible combinations of concurrency bugs you can imagine.

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 that they can also be combined - either within a single process (like tokio in rust). Or via multi-process configurations (eg one nodejs instance per core, all behind nginx). But they don't need to be. Go (green threads) and Nodejs (async, single threads) both work well.

Of course we're comparing them. We all want to know who wore it better

Re: What async promised and what it delivered

#227
post #39

How many systems are there that can't just spawn a thread for each task they have to work on concurrently? This has to be a system that is A) CPU or memory bound (since async doesn't make disk or network IO faster) and B) must work on ~tens of thousands of tasks concurrently, i.e. can't just queue up tasks and work on only a small number concurrently. The only meaningful example I can come up with are load balancers,…

Async does make nvme io faster because queueing multiple operations on the nvme itself is faster.

This is outside of my expertise, but wouldn't multiple threads each submitting a single operation in parallel have the same effect?

Re: What async promised and what it delivered

#228

Earlier quoted context omitted.

I'm not sure this is correct mental model of what async solves Async precisely improves disk/network I/O-bound applications because synchronous code has to waste a whole thread sitting around waiting for an I/O response (each with its own stack memory and scheduler overhead), and in something like an application server there will be many incoming requests doing so in parallel. Cancellation is also easier with async C…

I have some test code that runs a comparison of Hyper pre-async (aka thread per request) vs async (via Tokio), and the pre-async version is able to process more requests per second in every scenario (I/o, CPU complex tasks, shared memory). I'll publish my results shortly. I did these as baselines because I'm testing finishing the User Managed Concurrency Groups proposal to the linux kernel which is an extension to pr…

Relevant prior work: https://github.com/jimblandy/context-switch

Re: What async promised and what it delivered

#229
post #94
post #58

Earlier quoted context omitted.

Function coloring does not mean that functions take parameters and have return values. Result is not a color. You can call a function that returns a Result from any other function. Errors as return values do not color a function, they're just return values. Async functions are colored because they force a change in the rest of the call stack, not just the caller. If you have a function nested ten levels deep and it c…

> If you are ten layers deep in a stack of synchronous functions and suddenly need to make an asynchronous call, the type signature of every individual function in the stack has to change. well, this isn't really true - at least for Rust: runtime.block_on(async{}); https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.htm...

In Kotlin, it’s runBlocking {}.

This is a language specific problem, not a language pattern one.

Re: What async promised and what it delivered

#230
post #227

Earlier quoted context omitted.

Async does make nvme io faster because queueing multiple operations on the nvme itself is faster.

This is outside of my expertise, but wouldn't multiple threads each submitting a single operation in parallel have the same effect?

That is still “async” considering what gp wrote.

Because they wrote “thread per task” which I assume to mean something like “each os thread handles the work submitted by one user”.

This is beside the point but, something like io_uring is still significantly better than doing threadpool nvme io.

Post reply on HN