Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Does the use of `std::sync::Mutex` across `await` point ALWAYS cause deadlock?

Though I have read all of the OP, the answer and the comments of Why do I get a deadlock when using Tokio with a std::sync::Mutex?, I don’t yet understand why the code in the OP blocks forever.

Here’s the slightly changed version of the original code:

use std::sync::Arc;
use std::sync::Mutex;
// use tokio::sync::Mutex;
use tokio::time::Duration;

async fn f(mtx: Arc<Mutex<i32>>, index: usize) {
    println!("{}: trying to lock...", index);
    {
        let mut v = mtx.lock().unwrap();
        // let mut v = mtx.lock().await;
        println!("{}: locked", index);
        tokio::time::sleep(Duration::from_millis(1)).await;
        *v += 1;
    }
    println!("{}: unlocked", index);
}

#[tokio::main]
async fn main() {
    let mtx = Arc::new(Mutex::new(0));
    tokio::join!(f(mtx.clone(), 1), f(mtx.clone(), 2));
}

The output is

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

1: trying to lock...
1: locked
2: trying to lock...

(and blocks forever...)

According to the answer and the comments (and if I read them correctly), the reason is the entire code is executed in a single-threaded environment. If the italicized part is true, I can understand the blocking behavior. However, I don’t understand if the italicized part is actually true in the first place.

As far as I understand,

  • the default runtime of Tokio is multi-threaded unless you explicitly specify #[tokio::main(flavor = "current_thread")] (source)

  • and awaited tasks can automatically be moved to another worker thread (source).

So I think the code does NOT block if the tasks (i.e. f(mtx.clone(), 1).await, f(mtx.clone(), 2).await and sleep(...).await) are chosen (by Tokio runtime) to be executed in different threads, but the code looks blocking as the runtime happens to choose the tasks are all executed in the same single thread.

Is my understanding correct?

>Solution :

the entire code is executed in a single-threaded environment

Indeed.

the default runtime of Tokio is multi-threaded

Yes, but that only applies to tasks. Tasks are like lightweight threads and they can execute in parallel on different OS threads. But tokio::join!() does not create new tasks. It is an async primitive that takes two (or more) futures and combine them into one via a state machine, on the same task. This has the advantage that it is more lightweight, but it also means that if you’re blocking in one of the futures all others will also be blocked. Therefore, it is good for really IO-bound code, if the code is CPU-bound even a little or you have many futures it is better to spawn a task.

Also note that tokio tasks may run on different threads, but this is not guaranteed. In particular, tokio has optimization heuristics that can cause only one thread to be used. In this case this code will also deadlock. Also, even if it will not deadlock, it is still blocking, and blocking should never be done in async environment.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading