Hacker News new | past | comments | ask | show | jobs | submit login

My possibly unpopular opinion is that async/await is a mistake as a first class programming construct. That functionality should 100% be in libraries for two reasons: 1) that way it won't infect the actual language in any way and 2) it will be more difficult to use so people will only reach for it if they really need it.

Sync code and threads is the way to go for 99% of the cases where concurrency is needed. Rust handles most of the footguns of that combination anyway.




Couldn't disagree more. In my experience, single-threaded event-loop driven async/await should be used for every possible concurrency need, with the complexity of multi-threaded concurrency being reserved for the rare cases it's needed. As auto-scaled services and FaaS began to become popular, I've found most any need for multithreaded programming almost wholly unnecessary.


Not everything is a web backend... CPU bound workloads may be rare in your domain, but I would be careful with generalizations.


When every problem looks like a nail, the only thing you need is an hammer!


Not only that but autoscaling with FAAS is an expensive, locked in and complex to administer way to provide functionality.


It's true though that in most cases single-threaded event-loop concurrency is sufficient. I wasn't attempting to make a generalization, I was saying that in cases where the CPU-bound work _is_ more voluminous, I prefer process-parallelism over threaded parallelism for such workloads (of which FaaS is just one possible impl).


> It's true though that in most cases single-threaded event-loop concurrency is sufficient.

This still sounds very web-centric.

In general, concurrency and parallelism are really orthogonal concepts. When threads are used for concurrency, e.g. to handle multiple I/O bound tasks, they do indeed compete with coroutines (async). But when we want parallelism, coroutines are not even an option. That's why it irks me when people compare threads with coroutines without specifying the exact use case.

> I prefer process-parallelism over threaded parallelism for such workloads

Process-parallelism is just a special case of multithreading and in many domains its not even a realistic option.


But coroutines _are_ an option for parallelism, and an especially effective one in the I/O-bound world. The parallelism comes from the OS handling I/O scheduling for the coroutines instead of the application code.

The important difference between multithreading and multiprocess is that I can ignore synchronization that's not IPC in multiprocess models which makes the code much much simpler to implement and reason about.

I wouldn't even say this is web-centric, process-parallelism is a pretty common method of task dispatch in HPC compute topologies that has filtered down to smaller scales of multi-server compute clusters a la kubernetes. In these cases, taking a process-centric message-passing approach can greatly simplify not only the code but the architectural aspects of scheduling and scaling that are quite a bit more difficult with multi-threaded processes or even those that mix I/O and CPU-bound work in the same process (which is often a cause of thread starvation issue in node apps).


> But coroutines _are_ an option for parallelism, and an especially effective one in the I/O-bound world.

Parallelism means that code executes simultaneously (on different CPUs). Coroutines (on a single threaded executor) don't do that.

> The parallelism comes from the OS handling I/O scheduling for the coroutines instead of the application code.

Where exactly is the parallelism that is enabled by coroutines? Coroutines are nothing more than resumable functions.

When it comes to I/O performance, threads vs. async is really a false dichotomy. What we should be comparing is

1. threads + blocking I/O

2. non-blocking I/O (select, poll, epoll)

3. asynchronous I/O (io_ring on Linux, IOCP on Windows)

Coroutines may only provide syntactic sugar for 2. and 3.


> The important difference between multithreading and multiprocess is that I can ignore synchronization that's not IPC

Sure, if the data allows it and you can afford the memory and time overhead. There is no silver bullet! There are cases where you must work on in-process shared memory and process-parallelism wouldn't even be possible (think of multithreading in video games or DAWs). Note that if the data is properly partitioned, you might not need any locking, even with "normal" threads.


The problem is that in Rust the main way of doing async/await (Tokio) is multithreaded by default. So you get all the problems of multithreading, and additionally all the problems with async/await. The discourse would be very different if the default choice of executor would have been thread-local.


Is this default really that big of a problem? Most people running Tokio are going to be running it on multicore hardware, and they'll get some additional throughput for free. If they're sufficiently resource constrained to the degree that they realize that a single thread is a better fit for their domain, they'll Google it and realize you can use Tokio's single-threaded runtime.

To be clear: it is a one-line change to make Tokio use the current (single) thread.

https://docs.rs/tokio-macros/latest/tokio_macros/attr.main.h...


The problem is most clearly visible in the difference between

https://docs.rs/tokio/1.36.0/tokio/runtime/struct.Runtime.ht...

https://docs.rs/async-executor/latest/async_executor/struct....

Those Send + 'static bounds have really big impact. Multithread is definitely not just "additional throughput for free", nor is the change between single to multi just one line of code.


Yes, this is a big problem in my mind and something I mentioned in another comment. Not that multi-threaded async/await is wrong, but the conflation of two methods of concurrency causes a lot of problems you don't see in single-threaded event-loops archs.

In fact it makes me feel like the other thread talking about how async/await in rust was mainly forced on the community to capture js devs seems a bit unlikely as the only thing similar between the two are the spelling of the keywords.


Sounds like a problem with Rust. Not a problem with async/await.


I'm actually more of a fan of the actor model than the single-threaded event-loop. Individual blobs of guaranteed synchronicity with message passing.


Perhaps an acceptable solution would be that all blocking things be async by convention, at least potentially. It's the conflict between sync and async that I find distasteful. The coloring problem.

Zig does almost something like this.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: