5 ms·
Multi threading and concurrency are not the same. You can have a very high performance server that handles thousands of requests concurrently on a single thread
by rklaehn 3y ago
Multi threading and concurrency are not the same. You can have a very high performance server that handles thousands of requests concurrently on a single thread. That's how node/deno do things.
But the way to do things in async rust is that if you want concurrency you also have to use multithreading. At least that is what you see in all the examples and docs.
As soon as you require your futures to be Send you have to use not just concurrency but full blown multithreading primitives.
So e.g. you have to use Arc<RwLock<...>> where a Rc<RefCell<...>> would suffice.
Now, to be fair, both futures and tokio support local futures. There is boxed_local (https://docs.rs/futures/latest/futures/future/trait.FutureExt.html#method.boxed_local https://docs.rs/futures/latest/futures/future/trait.FutureEx...) and tokio-util even comes with an optional local task pool (https://docs.rs/tokio-util/latest/tokio_util/task/struct.LocalPoolHandle.html https://docs.rs/tokio-util/latest/tokio_util/task/struct.Loc...).
But these features are hidden and certainly not promoted in examples and documentation.
- menaerus 3y ago> So e.g. you have to use Arc<RwLock<...>> In terms of C++ code that would equate to std::shared_ptr<std::shared_mutex<...>> which ... sounds quite wasteful in terms of scalability/performance. Why is it not possible to simply return a Rust promise? That's the way I do it in my C++ async (executor) library backed by work-stealing queues under the hood.
- masklinn 3y ago> Why is it not possible to simply return a Rust promise? It… is? An Arc<RwLock> is what you need to share mutable data between “promises”.
- deleted 3y ago[deleted]
- menaerus 3y agoI understand that part. What I don't understand is why https://docs.rs/promises/latest/promises/struct.Promise.html https://docs.rs/promises/latest/promises/struct.Promise.html are not used instead.
- masklinn 3y agoBecause there is no “instead”? A promise (or really a task if you’re in an async context, what you link to is really a thread + a pseudo-oneshot channel) allows independently scheduling a unit of work, it’s neither shared nor mutable state. The promise object you link to is not Clone, so there is no way for two different threads to refer to the same one without adding an intermediate `Arc`, and because all the methods on it operate by value that’s not exactly helpful either as you can’t move the promise out of the Arc. Same with a tokio::task::Task. A JoinHandle can’t be duplicated (though according to its doc on some platforms it might be duplicatable — if the thread returned a duplicatable value anyway).
- rklaehn 3y agoNot sure what you mean by "returning a rust promise". But yes, Arc<Mutex<...>> is a thread safe smart pointer containing a mutex, which has quite some overhead. You will see this in many places in the internals of async code that has to be Send. OK in many cases, but it can kill you if you have to go through this for many small operations.
- menaerus 3y agoYes, that's what I meant - it's a heavy operation consisting of multiple dynamic memory allocations and atomics. Former can be somewhat addressed through custom allocators and which in my case proved to be a big win for small and short-living tasks.
- duped 3y agoI think it's worth understanding why you need Arc and RwLock here. If the compiler fails to type check because a future is not Send, it means that the future's context holds onto some state that is not safe to be sent to a different thread. For example, holding a reference to something on the stack across an await point. If it fails because something is not Sync, it means the future's context holds onto some state that is potentially accessed concurrently from multiple threads. If you write your async code such that it does not have these concurrency bugs then you don't have a problem. Arc is a convenient way to make something that is Send and RwLock is a convenient way to make something Sync. You can also fix your code by not introducing concurrency bugs. This isn't perfect though because the errors are often cryptic. You can have a function that is fine in one context completely break when called in another with "future is not send". And good luck identifying where the bug is and how to fix it. As for constructing a future directly, sure you can do that. But keep in mind Rust futures aren't promises that just wrap some .then callback. They are types that implement the Future trait which has a method .poll that takes self by Pin and a Waker object to prevent the underlying data from being moved and invalidating references as well as wake other pending futures. If that sounds complex, it is, because async in Rust is actually super complicated and the syntactic sugar does a massive amount of work.
- vacuity 3y agoThe lengths that the Rust devs went through to minimize the overhead of async/await is impressive but also horrifying. And unfortunately, async/await certainly isn't a zero-overhead abstraction if you consider ergonomics/learnability. I wonder if it can evolve without breaking backwards compatibility.
- VWWHFSfQ 3y agoI believe you can just annotate your tokio::main with flavor = "current_thread"