Lock on Mutex.. can you explain what every line is doing?

Hello,
So first of all the documentation for the lock function seems quite bad to me.. someone want to improve it?

Anyway, looking at this snippet, I'm wondering if you can explain what every line does:
let mutex = Arc::new(Mutex::new(0));
let c_mutex = mutex.clone();
*c_mutex.lock().unwrap() = 10;

Why 0 in Mutex::new(0) ? Does that specify which mutex to use?

Why cloning the mutex instead of just directly using it?

And finally, why 10 for the lock? Does that make it like a semaphore with a counter of 10?

In Rust, mutexes also own the value that they guard, as opposed to C++ mutexes,which just lock and unlock a system mutex. Because of this, when you lock a mutex you get a smart pointer back, which you can safely use to access the value without worrying about another thread using the same memory.

The first line creates a mutex which owns the value 0, and puts it in a atomically reference counted smart pointer (Arc).
The second line clones the Arc. (I don't know why they are showing this)
The third line locks the mutex and gets a mutex guard (smart pointer) and uses the mutex guard to access a mutable reference to the underlying value (in this case 0), and uses that mutable reference to change the underlying value to 10.

2 Likes

Everything that @RustyYato said is true. I assume you are referring to this example. It is important to clone because thread::spawn uses move semantics. If you hadn't cloned, you cannot use mutex later in the assert!.

1 Like

@AlwaysLearning Yes that is what we are referring to. Ok, so we don't need that if we are just locking on the current function and not spawning a separate thread. Good to know.

@RustyYato
Ok, but if the mutex guard had set that value to anything other than 0, it'd function the exact same as setting it to 10?

Thank you both of you! The best part of rust is the community :slight_smile:

1 Like

Yes, the code sets the mutex value to 10, (the initial value is irrelevant)

1 Like

Completely irrelevant what number it sets the mutex too? Or does it need to be anything but 0, which it seems to initially be set to?

Why not simply call:
let mutex = Arc::new(Mutex::new(10));
*mutex.lock();

For this example, by the time the program ends, it doesn't matter what the initial value of the mutex is, at the end it will be 10. This is because the thread that is spawned is forced to join, therefore forcing the mutex to be written to before reading it.

But in general, it may matter what your initial value is.

You seem to be assuming that the mutex is a stand-alone primitive. But in Rust mutex is a wrapper around the value it protects.

In other languages you'd have:

let mutex = Mutex::new();
let value_protected_by_mutex = "hello";

mutex.lock();
value_protected_by_mutex = "world";
mutex.unlock();

In Rust you have:

let value_in_a_mutex = Mutex::new("hello");

let value_by_proxy = value_in_a_mutex.lock();
*value_by_proxy = "world";

So mutex is not a separate concurrency primitive. Mutex is kind of a string/integer/array/struct/etc. that handles its own synchronization for itself only.

3 Likes

There are two wrappers here:

  1. Arc<_>, a life-extending smart pointer which guarantees that the data it points to is not dropped (thus preventing use-after-free bugs and vulnerabilities). Its main property is that Clone-ing it is very cheap (all it does is copying the address and (atomically) incrementing a counter).

    • Since ::std::thread::spawn() creates a thread that can outlive the one that spawned it, there is a real danger of use-after-free, thus making the usage of Arc mandatory when trying to share locals (i.e. not static variables);

    • In my example I will use scope.spawn() from ::crossbeam::scope(), since it yields scoped threads that are guaranteed not to outlive their spawning threads, thus making Arc unnecessary (simple &_ (and &mut _) references suffice);

  2. Mutex<_>, to which the following example is dedicated:

use ::std::sync::Mutex;

fn main ()
{
    // Let's create a string
    let message = String::from("Hello");

    // now, since we wish to both share and mutate this string,
    // we need some synchronization primitive to do it soundly.
    // For instance, a Mutex:
    let mutex_guarding_message: Mutex<String> =
        Mutex::new(message)
    ;

    // (scoped) CONCURRENCY
    ::crossbeam::scope(|scope| {
        // spawn new (scoped) thread
        scope.spawn(|_| {
            // in order to get access to the message, we need to use Mutex's API
            // and lock() it. This will give us a handle that not only behaves as message,
            // but it also releases the lock once it is dropped (e.g. when exiting a scope)
            let at_message =
                mutex_guarding_message
                    .lock()
                    .expect("Lock was poisoned")
            ;
            println!("message = {:?}", *at_message);
        }); // at_message is dropped here, releasing the lock
    
        // spawn new (scoped) thread
        scope.spawn(|_| {
            // withing that thread, do:
            let mut at_message =
                mutex_guarding_message
                    .lock()
                    .expect("Lock was poisoned")
            ;
            at_message.push_str(", World!");
        }); // at_message is dropped here, releasing the lock

    }) // all (scoped) threads are automatigally join()ed here (crossbeam::scope)
     .expect("Some scoped thread has panicked");

    {
        let at_message =
            mutex_guarding_message
                .lock()
                .expect("Lock was poisoned")
        ;
        println!("\nAt the end:\n  message = {:?}", *at_message);
    } // at_message is dropped here, releasing the lock
}
  • Playground (since my code has (on purpose) a race condition, if you run it several times you should observe different behaviors (but all sound!))

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.