async isn't only about performance, but has other advantages, like reduced resource consumption. In addition to that async io also gives you better control over how to cancel io reads and writes on systems where the IO is not interruptible.
But you are correct, if you don't have a specific need, async is generally harder than using threads for concurrency. Ideally the async/await work in Rust is going to make that trade-off less extreme than it is today, which may mean more people will feel comfortable using it as it should reduce boiler plate.
A fun example is the slow loris attack. You send your requests character by character with 20+ seconds between packets. For mobile 20 seconds can happen so if the server doesn't use a running window to check timeouts it can't kill the connection.
Now you do this 1000 times you use barely any resources but the server uses 1GB in stack allocations which might be the maximum and noone else can connect.
You can think of a task as being a thread, but it has one single allocation that’s the exact possible stack size. No more, no less. This uses less memory than spinning up a thread with the default stack size. Yes, you could use the proper APIs and get the correct size too, but you have to figure that size out by hand for each thread. It just implicitly happens with tasks.
Hm, maybe I misunderstand what you're getting at; you're talking about one thread per core, not one thread per unit of work? Sure, if you only have that few threads, then it's not that big of a difference, but if you want to spin up a few hundred thousand of them...
> you're talking about one thread per core, not one thread per unit of work?
Yes, a thread pool, consisting of one thread per core/computing unit. The units of work are then scheduled between the threads. Units of work here being some kind of IO, e.g. servicing HTTP requests.
> but if you want to spin up a few hundred thousand of them...
Hm. Thought there was a limit for work that can be done concurrently by the CPU, based on the number of cores/hyper-threads available. Found this on threads and IO performance [1], it seems to make the same point.
What kind of work load is common to spread over so many threads (on the same machine)? Does the OS switch efficiently between hundred of threads on regular CPUs? Genuinely interested.
Okay so, there are lots of ways to do this kind of stuff. A threadpool is a pretty classic one. Apache being the poster child here in an HTTP server context.
> Thought there was a limit for work that can be done concurrently by the CPU,
Right. But in an IO bound scenario, the CPU isn't doing work; it's waiting on IO. So, because threads are generally heavy, you don't want a ton of them, taking up memory, doing nothing.
But, when you have lightweight threads, you can spin up one per connection. This ends up being simpler, and you don't have the large memory usage. This is what nginx does, in a sense. It still has a worker per core, but each of those workers can handle thousands of requests simultaneously, because it's all non-blocking.
That limit to concurrent work is exactly why non-blocking architectures are so important, and task systems fit into them really nicely.
Excellently said, Steve. This is a great thing to know in this context, “Latency numbers ever programmer should know”: https://gist.github.com/jboner/2841832
Yeah okay. That’s a way to do things too, of course. I have some stuff to say but I’m about to go get some dinner, I’ll reply for real later. (And I’m sure others in this thread have opinions too)
(I also misinterpreted the context as I read the top level comment as async vs a single thread with blocking code, but after rereading it that makes more sense.)
It's all good; it's one of the things that's specific to our implementation. Other forms may or may not do this, but I'm pretty sure that it's novel to at least Rust, and maybe C++; there's some discussion that I think it can do this in some circumstances as well.
But you are correct, if you don't have a specific need, async is generally harder than using threads for concurrency. Ideally the async/await work in Rust is going to make that trade-off less extreme than it is today, which may mean more people will feel comfortable using it as it should reduce boiler plate.