Is a Mutex Ever Used Alone?

Mutex always seems to be used inside an Arc.

As I understand it an Arc counts runtime references in lieu of a compile time analysis of ownership. But the contents of an Arc are immutable. That's where a Mutex comes in, it has interior mutability.

Is it ever sensible to use a Mutex alone?

Maybe there could be some recursion case where some code might interfere with itself? So it calls try_lock() and works on some other other aspect of the problem in that case to avoid self-interference?

An Arc might be used alone for read-only data. Would that be sensible?

A related question is when might Arc and Mutex might be used together, but not one-to-one: Might a single Arc sensibly contain multiple Mutexes? That way different threads could be messing with different aspects of of some larger data, but the existence of that collection of data still be a single thing, counted by a single Arc.

Thanks,

-kb

I think it always ends up inside an Arc, but you could have more than one Mutex in the Arc, or a combination of a Mutex and a RwLock as here:

pub struct SharedPagedData {
    pub file: RwLock<CompactFile>,
    pub sp_size: usize,
    pub ep_size: usize,
    pub stash: Mutex<Stash>,
}
3 Likes

It's not always an Arc, but it is always inside a shared reference. This is true of anything using UnsafeCell since the only thing it does is remove the guarantee that an & reference implies immutability.

Arc is useful because it allows you to make shared references that live forever. But you can also make those with static:

static GLOBAL: Mutex<i32> = Mutex::new(0);

Another way is with Box::leak.

And you can also use mutexes that don't live forever, although you also need something like std::thread::scope or rayon so that you can use multiple threads without needing the reference to live forever.

let mutex = &Mutex::new(0);
std::thread::scope(|s| {
    for i in 0..10 {
        s.spawn(move || *mutex.lock().unwrap() += i);
    }
});
12 Likes

Not actually a contradiction, but you can convert &mut T to &Cell<T> (which might be easy to overlook if one's "UnsafeCell :left_right_arrow: shared" association is overly strong).

1 Like

This has been very useful.

Let me state my current understanding. Explaining something helps me nail down that I really get it.

The classic use of a Mutex is inside an Arc. The Mutex provides the multiple access policing and an Arc is used…because lifetimes; it allows there to be multiple access in the first place. An Arc—or something else—has to convince the compiler the Mutex will live long enough for all of its users to access it. But, as has been pointed out, the shared access need not be via an Arc, it could be some other container.

Or, it could be a global. I really like the global example for the ah-hah that it is lifetimes that are important and not that Arc or some other container is a magic talisman. Whatever it turns out to be, something needs to convince the compiler that none of the Mutex's users can crash because the shared data went away too soon.

But it is not strictly necessary for a Mutex to be inside some shared reference. A single user can mess with a Mutex (example). It seems that would be pretty pointless beyond poking at the Mutex for sadistic purposes—perhaps benchmarking, or helping someone like me understand the whys of Rust. But it is possible.

As for my musing that there might be some way for a recursive algorithm to share mutable data with itself and have that data in a Mutex? Sure, maybe, maybe…but if so it just means I found an obscure (and likely stupid) way to share data.

The point of a Mutex is to police access to shared the data, and there are various ways to share data. An Arc is a good one, but not the only one.

Thanks to all!

-kb, the Kent who has learned a lot.

1 Like

It has uses; e.g. you send out a Arc<Mutex<Data>> to multiple workers but want to reclaim the bare Data (via deconstruction) when the work is done (Arc strong count is 1).

That's why the self and &mut self methods on Mutex exist.

3 Likes

So turn Arc<Mutex<Data>> into Data. But not turn it into just Mutex<Data>, right?

Using std::thread::scope makes it a lot easier to share a Mutex across threads without Arc, and much of rayon's API allows borrowed lifetimes as well.

3 Likes

I can't think of anything where ownership would be useful, but you can do Arc<Mutex<Data>> -> &mut Mutex<Data> -> &mut Data which may be useful since you don't need to move Data.

1 Like

Arc<Mutex<Data>> to Mutex<Data> to Data. Arc doesn't know about Mutexes internals.

1 Like

It's not always used inside an Arc but it's used this way so often I think there should be a single type for it (say, Arx<T>).