We still need them if we have multiple parallel tasks (coroutines spawned non-locally) using a shared resource across multiple yield points.
As long as the accesses to the shared variable are separated in time, sharing is fine.
This is correct code:
let mut foo = 1;
async { foo += 1 }.await;
foo += 1;
println!("{foo}");
See - a shared variable used across multiple yield points.
Another (more useful) example I showed below in another post with `select!`. int foo = 1;
std::thread ([&] { foo+=1; }).join();
foo+=1;
std::cout <<foo <<'\n';
(sorry for the C++, I don't speak much rust). mut buffer: &[u8] = ...;
loop {
select! {
_ = stream.readable() => stream.read(&mut buffer),
_ = stream.writable() => stream.write(&mut buffer),
}
} select! {
_ = channel.readable(), if queue.has_free_space() => read(&mut queue),
_ = channel.writable(), if queue.has_data() => write(&mut queue),
}
The point is I can implement `has_free_space` and `has_data` without thinking about concurrency / parallelism / threads. I don't need to even think what happens if in the middle of my "has_free_space" check another thread goes in and adds some data. And I don't need to invoke any costly locks or atomic operations there to ensure proper safety of my queue structure. Just purely sequential logic. Which is way simpler to reason about than any SPSC queue.In any case acq/rel is the only thing required here and it is extremely cheap.
edit: in any case we are discussing synchronization and 'has_free_space' 'had_data' are a form of synchronization, we all agree that async and threads have different performance characteristics.
I don't think it is a universal solution. What if I am interested in parallelism as well, only not for the coroutines that operate on the same data? If my app handles 10k connections, I want them to be handled in parallel, as they do not share anything so making them parallel is easy. What is not easy is running stuff concurrently on shared data - that requires some form of synchronization and async/await with event loops is a very elegant solution.
You say that it can be handled with an SPSC queue and it is only one ack/rel. But then add another type of event that can happen concurrently, e.g. a user request to reconfigure the app. Or an inactivity timeout. I can trivially handle those with adding more branches to the `select!`, and my code still stays easy to follow. With threads dedicated to each type of concurrent action and trying to update state of the app directly I imagine this can get hairy pretty quickly.