0

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?)

cafce25
  • 15,907
  • 4
  • 25
  • 31
MrZlo
  • 9
  • 3
  • Nit: your usage of `join!()` is incorrect. `join!()` awaits _multiple_ futures at once, but you only have one with it. You can just `.await` both. – Chayim Friedman Jul 18 '23 at 09:53
  • 1
    Another nitpick: `(*read).n` -> `read.n`, same for `write.n`. – Chayim Friedman Jul 18 '23 at 09:58
  • 2
    The fact that here you have a bug that also happens with normal threads doesn't mean that they are the same. Don't let that deceive you. Async programming is way harder than threads, at least in Rust. – Chayim Friedman Jul 18 '23 at 09:59

1 Answers1

3

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;
    }
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Depending on the actual types you might even want to consider using separate locks for reading and writing that way even more reads can potentially happen and you don't need the `drop` since it happens at the end of the statement with something like `w.write().await.n += 2; println!("{}", w.read().await.n);` anyways. – cafce25 Jul 18 '23 at 10:03