asynchronousrustrust-tokiorwlock

Correct usage of async with tokio in Rust


I never worked with asynchronous code (but have exp with multithreading) in Rust. I thought it is mostly the same thing, however I got some problems. Here listing of my sample:

use tokio::{join, sync::RwLock};
use std::sync::Arc;
use tokio::time::{sleep, Duration};

struct MyThing {
    n: usize,
}

impl MyThing {
    fn new(x: usize) -> MyThing {
        MyThing{ n: x }
    }
}

async fn rr(v: Arc<RwLock<MyThing>>) {
    loop {
        let read = v.read().await;
        println!("Value : {}", (*read).n);
        // sleep(Duration::from_millis(50)).await;
    }
}

async fn ww(w: Arc<RwLock<MyThing>>) {
    loop {
            let mut write = w.write().await;
            (*write).n += 2;
            println!("Updated value: {}", (*write).n);
            sleep(Duration::from_millis(200)).await;
        } 
}

#[tokio::main]
async fn main() {
    let x = Arc::new(RwLock::new(MyThing::new(11)));
    let w = x.clone();
    let r = x.clone();
    let h = tokio::spawn(async move {
        ww(w).await
    });
    let j = tokio::spawn(async move {
        rr(r).await
    });
    
    join!(h);
    join!(j);
}

So i was expecting that I would get 2 async "threads", and while ww is sleeping I will get ton of readings, but, im getting next output:

Updated value: 13
Value : 13
Updated value: 15
Value : 15
Updated value: 17
Value : 17
Updated value: 19
Value : 19
Updated value: 21
...

So my question is: Is it correct behavior or I just dumb? (And additional question, if i delete await in tokio::spawn after i call function, those functions rr and ww aren't called?)


Solution

  • You're sleeping while holding the lock. This bug can be caused in threaded programming too.

    Because you don't drop the lock (write), it is not released, and rr() cannot acquire a read lock until after the sleep.

    Drop the lock before the sleep, and you'll see the expected output:

    async fn ww(w: Arc<RwLock<MyThing>>) {
        loop {
            let mut write = w.write().await;
            (*write).n += 2;
            println!("Updated value: {}", (*write).n);
            drop(write);
            sleep(Duration::from_millis(200)).await;
        }
    }