limp_chimp
limp_chimp

Reputation: 15163

do mutex's in C++ have to be tied to some object or variable?

I'm somewhat new to threading and I'm trying to understand how it works in C++11. The professor in my class gave us this sample code to demonstrate the use of a mutex:

#include <list> 
#include <mutex> 
#include <algorithm>

std::list<int> some_list; // A data structure accessed by multiple threads
std::mutex some_mutex; // This lock will prevent concurrent access to the shared data structure

void
add_to_list(int new_value) {
    std::lock_guard<std::mutex> guard(some_mutex); // Since I am going to access the shared data struct, acquire the lock
    some_list.push_back(new_value); // Now it is safe to use some_list. RAII automatically releases lock at end of function }
}

bool
list_contains(int value_to_find) {
    std::lock_guard<std::mutex> guard(some_mutex); // Must get lock every time I access some_list return
    std::find (some_list.begin(),some_list.end(),value_to_find) != some_list.end();
}

I think the code is somewhat self-explanatory but I had some specific questions.

  1. Is there no need to specifically associate the mutex with the list?
  2. And if not, does that mean that any time a mutex is used, all threads halt until the mutex is destroyed? Or is it only a subset of threads; perhaps threads in some threadpool or otherwise associated with each other?
  3. And whichever is the case, isn't it better to only halt threads which are attempting to access the data structure? Because otherwise, we're not worried about data races and the like.
  4. Finally, what's the difference between a mutex and a lock? Is a mutex simply an RAII lock? Or is the RAII happening via the guard?

Upvotes: 9

Views: 3735

Answers (4)

Dietrich Epp
Dietrich Epp

Reputation: 213308

  1. The mutex is associated with the list, but this association is completely manual -- the compiler and runtime library do not know that the two are associated. The association exists entirely in your documentation and in your head, and you are responsible for ensuring that any thread which accesses the list locks/acquires the mutex first.

  2. Whenever a mutex is used, the thread which locks/acquires the mutex will halt (the term is actually block) until no other thread owns the mutex. Threads which are not using the mutex will be unaffected.

  3. You are responsible for ensuring that only the threads which access the list lock/acquire the mutex, and you are also responsible for ensuring that all of the threads which access the list lock/acquire the mutex. Again, only those threads can block waiting for the mutex.

  4. The same object goes by a number of different names: "mutex", "lock", or "critical section". The guard uses RAII to lock/acquire the mutex.

Upvotes: 6

Nemanja Boric
Nemanja Boric

Reputation: 22157

Is there no need to specifically associate the mutex with the list?

No, there is not. In this context, mutex is simply guarding block of code, not the list itself. Because some_list.push_back() and std::find are executed in the blocks of code which are guarded by the same mutex, separate threads will not propagate together in guarded blocks, until one threads exits the block.

And if not, does that mean that any time a mutex is used, all threads halt until the mutex is destroyed? Or is it only a subset of threads; perhaps threads in some threadpool or otherwise associated with each other?

No - all threads that are trying to enter the block guarded by mutex are suspended until mutex is unlocked (which can happen by destroying lock_guard object holding the mutex).

And whichever is the case, isn't it better to only halt threads which are attempting to access the data structure? Because otherwise, we're not worried about data races and the like.

As I said, only threads that are trying to access guarded blocks are potentially suspended, so there is no need to block all threads.

Finally, what's the difference between a mutex and a lock? Is a mutex simply an RAII lock? Or is the RAII happening via the guard?

No, mutex is simply synchronization primitive which is implemented differently on different systems, but C++ is providing unified interface to it. mutex itself only knows three operations: lock, try_lock and unlock, so there are different wrappers around it. One of them, which provides RAII is std::lock_guard.

Upvotes: 3

Andy Prowl
Andy Prowl

Reputation: 126432

Is there no need to specifically associate the mutex with the list?

No, or at least not explicitly, and neither is this possible in general in a declarative way. Your code will have to (try to) acquire the appropriate mutex every time it needs to access an object protected by that mutex, and release it after it is done reading or altering its state. Knowledge about which mutex protects which object is only in a programmer's mind (and in a program's documentation, of course).

And if not, does that mean that any time a mutex is used, all threads halt until the mutex is destroyed? Or is it only a subset of threads; perhaps threads in some threadpool or otherwise associated with each other?

This means that all threads which want to perform an atomic sequence of operations on a certain object will first have to acquire the mutex which protects that object to synchronize access with other threads competing for the same resource. A mutex guarantees that only one thread can own it, and until this thread releases (not "destroys") the mutex, all other threads will be waiting to acquire it.

And whichever is the case, isn't it better to only halt threads which are attempting to access the data structure? Because otherwise, we're not worried about data races and the like.

Yes, indeed. By forcing the threads (and only those threads) which require exclusive access to a certain object to acquire the mutex that protects it, you do not force other threads to halt.

Finally, what's the difference between a mutex and a lock? Is a mutex simply an RAII lock? Or is the RAII happening via the guard?

A mutex is the synchronization object that a client may (try to) acquire to perform certain operations on an object protected by that mutex without worrying of other threads interfering with its operations (as long as the other threads also respect the protocol of trying to acquire the mutex before accessing the object, of course).

A lock is usually understood as a RAII wrapper object that encapsulates your locking of the mutex, and whose destructor automatically unlocks the mutex when invoked. Thus, when the lock goes out of scope (because returning from a function, or because an exception is thrown, etc.), the acquired mutex is automatically released.

Upvotes: 3

Guy Sirton
Guy Sirton

Reputation: 8401

Is there no need to specifically associate the mutex with the list?

No. You manually do that.

And if not, does that mean that any time a mutex is used, all threads halt until the mutex is destroyed? Or is it only a subset of threads; perhaps threads in some threadpool or otherwise associated with each other?

mutex = mutual exclusion. If two threads try to lock the mutex one of them will block until that mutex is released. If you try and lock a mutex while another thread has a lock it will block until it is released.

And whichever is the case, isn't it better to only halt threads which are attempting to access the data structure? Because otherwise, we're not worried about data races and the like.

Yes. You should only lock a mutex that is protecting a data structure while there is an issue with concurrent access. E.g. when modifying the data structure. This is why your add_to_list function only locks the mutex (some_mutex) for the duration of some_list.push_back().

Finally, what's the difference between a mutex and a lock? Is a mutex simply an RAII lock?

The C++ lock_guard is an RAII wrapper around a mutex. When the object is created the mutex is locked and when it is destroyed (goes out of scope) the mutex is unlocked.

Upvotes: 2

Related Questions