Skip to content

Using Mutexes and Lock Guards

Related
  1. Using Threads in C++
  2. C++ Patterns - Thread-safe Queue

A mutex (mutual exclusion) is a lockable object that is designed to signal when critical sections of code need exclusive access. Mutexes are used to prevent multiple threads from accessing shared data. Since threads execute on different CPUs at the same time, the mutex effectively ensures that only one core holds the lock at any time.

Attention

When using mutexes with a custom type, an important design consideration is whether you want the lock to exist within the class (in which can it can be a member variable initialized in the constructor of the class), or initalized outside the class and passed to the constructor. The former case implies that no other classes also need access to the shared memory. If this is not the case, it is incorrect to initialize the mutex within the class itself. See Examples.

Which Lock to Use?

Read the accepted answer here for appropriate uses of std::scoped_lock, std:lock_guard, and std::unqiue_lock: std::lock_guard or std::scoped_lock?

Use the simplest tool for the job
  1. lock_guard if you need to lock exactly 1 mutex for an entire scope.
  2. scoped_lock if you need to lock more than 1 mutexes for an entire scope.
  3. unique_lock if you need to unlock within the scope of the block (which includes use with a condition_variable).

However, contention lies around this point, and my personal preference is to use scoped_lock for everything unless unique_lock is required.

Examples

Example: Scoped lock with a function

c++
#include <mutex>

std::mutex a;
std::mutex b;

void worker() {
    std::scoped_lock guard(a, b);
    // Do some thing that is guarded by the lock
    // RAII lock is freed when scope is left
}
#include <mutex>

std::mutex a;
std::mutex b;

void worker() {
    std::scoped_lock guard(a, b);
    // Do some thing that is guarded by the lock
    // RAII lock is freed when scope is left
}

Example: Unqiue/scoped lock passed to a class

cpp
/// worker.h
class Worker {
public:
	// bind reference to mutex in initializer list
    Worker(std::mutex& mtx) : _mtx(mtx){} 
    void start();
private:
    std::mutex& _mtx;
};

/// worker.cpp
void Worker::start() {
	// lock the shared resource
    std::scoped_lock<std::mutex> guard(_mtx);
    // Do some thing that is guarded by the lock
    // RAII lock is freed when scope is left
}

/// main.cpp
int main() {
    std::mutex mtx;
    // create a vector of workers that all share the mutex
    std::vector<Worker> workers(4, Worker(std::ref(mtx)));
    // ...
}
/// worker.h
class Worker {
public:
	// bind reference to mutex in initializer list
    Worker(std::mutex& mtx) : _mtx(mtx){} 
    void start();
private:
    std::mutex& _mtx;
};

/// worker.cpp
void Worker::start() {
	// lock the shared resource
    std::scoped_lock<std::mutex> guard(_mtx);
    // Do some thing that is guarded by the lock
    // RAII lock is freed when scope is left
}

/// main.cpp
int main() {
    std::mutex mtx;
    // create a vector of workers that all share the mutex
    std::vector<Worker> workers(4, Worker(std::ref(mtx)));
    // ...
}

Example: Unqiue/scoped lock initialized in a class

cpp
/// worker.h
class Worker {
public:
	// instantiate internal mutex in initializer list
    Worker() : _mtx() {}
    void start();
private:
    std::mutex _mtx;
};

/// worker.cpp
void Worker::start() {
	// lock the shared resource
    std::unique_lock<std::mutex> guard(_mtx);
    // Do something with it here, RAII lock is
    // freed when scope is left
}

/// main.cpp
int main() {
    std::vector<Worker> workers(4, Worker());
    // ...
}
/// worker.h
class Worker {
public:
	// instantiate internal mutex in initializer list
    Worker() : _mtx() {}
    void start();
private:
    std::mutex _mtx;
};

/// worker.cpp
void Worker::start() {
	// lock the shared resource
    std::unique_lock<std::mutex> guard(_mtx);
    // Do something with it here, RAII lock is
    // freed when scope is left
}

/// main.cpp
int main() {
    std::vector<Worker> workers(4, Worker());
    // ...
}

Appendix

RESOURCES