Home > Blockchain >  How to achieve parallel execution of threads and update shared resources in Rust
How to achieve parallel execution of threads and update shared resources in Rust

Time:01-06

I am new to Rust and currently exploring concurrency and parallel execution of threads.

In the the following program I am trying to increment counter in three different threads randomly.

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main()
{
    let counter = Arc::new(Mutex::new(0));

    let counter_clone1 = Arc::clone(&counter);
    let t1 = thread::spawn(move ||
    {
        for _ in 0..5
        {
            let mut num = counter_clone1.lock().unwrap();
            *num  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 1 value --> {}", *num);
        }
    });

    let counter_clone2 = Arc::clone(&counter);
    let t2 = thread::spawn(move ||
    {
        for _ in 0..5
        {
            let mut num = counter_clone2.lock().unwrap();
            *num  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 2 value --> {}", *num);
        }
    });

    let counter_clone3 = Arc::clone(&counter);
    let t3= thread::spawn(move ||
    {
        for _ in 0..5
        {
             let mut num = counter_clone3.lock().unwrap();
            *num  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 3 value --> {}", *num);
        }
    });

    t1.join().unwrap();
    t2.join().unwrap();
    t3.join().unwrap();

    // print final value of counter here
    println!("final value of counter --> {}", counter.lock().unwrap());
}

When I run the program I see following output. The order of execution varies in different run, i.e. sometimes I see thread 1, 2, 3 and sometimes 1, 3, 2. However all the times the complete loop of a thread gets executed.

To my understanding the mutex should get unlocked when one iteration is finished and num goes out of scope.

Someone please explain.

printing from the thread 1 value --> 1
printing from the thread 1 value --> 2
printing from the thread 1 value --> 3
printing from the thread 1 value --> 4
printing from the thread 1 value --> 5
printing from the thread 2 value --> 6
printing from the thread 2 value --> 7
printing from the thread 2 value --> 8
printing from the thread 2 value --> 9
printing from the thread 2 value --> 10
printing from the thread 3 value --> 11
printing from the thread 3 value --> 12
printing from the thread 3 value --> 13
printing from the thread 3 value --> 14
printing from the thread 3 value --> 15
final value of counter --> 15

Below is my expected output (random).

printing from the thread 1 value --> 1
printing from the thread 2 value --> 2
printing from the thread 3 value --> 3
printing from the thread 1 value --> 4
printing from the thread 1 value --> 5
printing from the thread 2 value --> 6
printing from the thread 3 value --> 7
printing from the thread 1 value --> 8
printing from the thread 2 value --> 9
printing from the thread 3 value --> 10
printing from the thread 2 value --> 11
printing from the thread 1 value --> 12
printing from the thread 3 value --> 13
printing from the thread 2 value --> 14
printing from the thread 3 value --> 15
final value of counter --> 15

CodePudding user response:

You're keeping the mutex locked while sleeping → only lock the mutex when you need it, i.e. get rid of num and use e.g. *counter_clone1.lock().unwrap() = 1; so that the mutex will only be locked when needed:

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main()
{
    let counter = Arc::new(Mutex::new(0));

    let counter_clone1 = Arc::clone(&counter);
    let t1 = thread::spawn(move ||
    {
        for _ in 0..5
        {
            *counter_clone1.lock().unwrap()  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 1 value --> {}", *counter_clone1.lock().unwrap());
        }
    });

    let counter_clone2 = Arc::clone(&counter);
    let t2 = thread::spawn(move ||
    {
        for _ in 0..5
        {
            *counter_clone2.lock().unwrap()  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 2 value --> {}", *counter_clone2.lock().unwrap());
        }
    });

    let counter_clone3 = Arc::clone(&counter);
    let t3= thread::spawn(move ||
    {
        for _ in 0..5
        {
            *counter_clone3.lock().unwrap()  = 1;
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 3 value --> {}", *counter_clone3.lock().unwrap());
        }
    });

    t1.join().unwrap();
    t2.join().unwrap();
    t3.join().unwrap();

    // print final value of counter here
    println!("final value of counter --> {}", counter.lock().unwrap());
}

Playground

CodePudding user response:

While I agree that @Jmb 's approach will work, I suggest a clearer (to me at least) focus on the issue, and to just add some braces:

    let counter_clone1 = Arc::clone(&counter);
    let t1 = thread::spawn(move ||
    {
        for _ in 0..5
        {
            {
                let mut num = counter_clone1.lock().unwrap();
                *num  = 1;
            }
            thread::sleep(Duration::from_millis(10));
            println!("printing from the thread 1 value --> {}", *counter_clone1.lock().unwrap());
        }
    });

I've added braces, and used Jmb's way to print the value out as well. But the braces emphasize even more when the mutex unlocks and doesn't hide the scope "inside" the single line.

I think this illustrates more what the issue with the OP's program was: num lasted until the end of the loop scope and through the sleep, rather than being unlocked prior to sleeping, which is the most important part IMO.

For a bonus, just add the braces but don't alter the print statement, and the compiler flags that num is out of scope. Which is what you want, but also shows how it was "alive" across the sleep, and why the re-locking to print it is necessary.

  • Related