Spawning 100 threads and incrementing a shared counter, can this be optimzed/improved to complete in 1 second?

Viewed 131

I wanted to learn how to write this Go code in Rust, the go code is here for reference: https://go.dev/play/p/j9osOG5xs1R

It basically launches 100 threads, and in each thread in loops 1000 times, sleeping for 1 millisecond on each iteration and also increments some shared state.

Since it sleeps for 1 millisecond, it should complete in 1 second.

In my Go version, I actually create 100 threads and it completes in about 1 second as expected.

My rust code currently only has 10 threads, and takes 10 seconds to complete.

I must be doing something wrong, any tips on improving the performance?

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    use std::time::Instant;
    let now = Instant::now();

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {

            let mut num = counter.lock().unwrap();

            for _ in 0..1000 {
                thread::sleep(Duration::from_millis(1));
                *num += 1;
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);
}

I even built the program in release mode also:

cargo build --release
./target/release/spawner

It for some reason takes over 10 seconds for only 10 threads...

1 Answers

Each thread locks counter for the entire 1 second it runs, blocking all of the threads from making any progress. This causes them to run sequentially rather than in parallel.

If you shrink the scope of the lock then they'll grab and release the mutex each millisecond, unblocking each other and allowing the program to finish in the expected 1 second.

let handle = thread::spawn(move || {
    for _ in 0..1000 {
        thread::sleep(Duration::from_millis(1));
        let mut num = counter.lock().unwrap();
        *num += 1;
    }
});

Output:

Result: 10000
Elapsed: 1.09s
Related