Sleeping of threads and dropping the mutex guard

Here is the code snippet taken from Mara Bos's book:

    let increment = Mutex::new(0);

    thread::scope(|s| 
    {
        for _ in 0..10 
        {
            s.spawn(||
            {
                let mut gurad = increment.lock().unwrap();
                for _ in 0..100
                {
                    *gurad += 1;
                }
                drop(gurad);
                thread::sleep(Duration::from_secs(1));
                
            });
        }
    });

This code snippet takes only about one second to execute. Because all the ten threads in this code snippet can execute their one second sleep at the same time. How?

My reasoning 01:
For loop spawned 10 threads. Any of the threads acquired the lock of Mutex, increments the guard 100 times, drops the guard, and sleeps for one second. So 10 threads take 10 seconds in total to execute this code snippet.

This reasoning clearly incorrect as this code snippet takes only a second to execute.

My reasoning 02:
For loop spawned 10 threads. Any of the threads acquires the lock of Mutex, increments the guard 100 times(while all other threads wait for the lock), and drops the guard, the rest 9 threads acquire the lock one by one hence increment the guard, when all the 10 threads are done with incrementing they all sleep at the same time for one second so it takes only one second for 10 threads to sleep for one second each.

But something bothers me to understand the reasoning 02. The thread that acquired the lock first does not finish its execution after dropping the guard. This seems true for the rest 9 threads too, when all of them are done with incrementing and dropping the guard, they all sleep for a second at the same time. How can it be that a thread is still in a live state after dropping the guard manually to sleep at a later point together with the rest of the threads?

Thanks.

Why would running a destructor (in this case the Mutex guard) be incompatible with the thread continuing? Does main end if you drop a string?

The other threads are waiting to acquire the guard, not waiting for some thread it magically knew had the guard to end.

3 Likes

This is correct

This is incorrect. When one thread drops the guard it can already start sleeping while the next one gets the lock. This means they don't start sleeping at the exact same time, nor that they will stop sleeping at the same time.

The reason it seemingly takes only one second is because incrementing the guard 100 times is extremely quick, so you don't (or can't!) even notice. Do you think you can notice a 0.0001 seconds difference in a task that takes ~1 second?

7 Likes

We drop the guard manually even before the thread executes the thread::sleep() function. We do not have to wait for the thread to finish its work so that the guard/lock gets unlocked to be locked by another thread.

So before a thread finishes its work fully(executing sleeping statement) another thread gets the lock/guard to start working which saves time.

Don't you think that the above-mentioned point has anything to do with the code snippet which only takes a second?

Here is another code snippet that takes exactly ten seconds to execute. It is slightly modifies version of question's code, we do not drop the guard manually here.

    let increment = Mutex::new(0);
    thread::scope(|s| 
    {
        for _ in 0..10 
        {
            s.spawn(||
            {
                let mut gurad = increment.lock().unwrap();
                for _ in 0..100
                {
                    *gurad += 1;
                }
                thread::sleep(Duration::from_secs(1));
                
            });
        }
    });

So we wait for the thread to finish its work completely, after which the lock/guard gets unlocked to be used by another thread.

Yes.

No, why would you conclude that? The whole point of threads is that they run in parallel. The threads first drop the lock guard (allowing other threads to continue), and only then do they sleep for a second.

1 Like

I'm not sure I fully understood this question. If you mean whether I think that this point:

is the reason why it takes only one second then yes, that's the reason.

It takes over one second. It will never take less on a correctly working system.

It is the counter protected by the guard that is incremented.

This is key to understanding that others haven't pointed out.
It is CPUs (processors) that execute. An operating system adds threads and has a scheduler.
A thread can be active (running on a processor) or idle.

Acquiring a lock is something your program tells the OS to do. If the lock can't be acquired (due to another holding it) then the OS makes that thread idle (until guard is dropped.)

Sleep is another action your program tells the OS to do. Thread is idle during sleep.

1 Like