Shared Mutex

  • std::shared_mutex is defined in <shared_mutex>

  • It can be locked in two different ways:

  • Exclusive lock

    • No other thread may acquire a lock
    • No other thread can enter a critical section
  • Shared lock

    • Other threads may acquire a shared lock
    • They can execute critical sections concurrently

Exclusive lock

  • std::lock_guard<std::shared_mutex>

  • std::unique_lock<std::shared_mutex>

  • Only this thread can execute a critical section

    • Other threads must wait until the thread releases its exclusive lock
  • It can only acquire an exclusive lock when the mutex is unlocked

    • If other threads have shared or exclusive locks
    • This thread must wait until all the locks are released

Shared lock

  • std::shared_lock<std::shared_mutex>
  • A thread which has a shared lock can enter a critical section
  • It can only acquire a shared lock if there are no exclusive locks
    • If another thread has an exclusive lock, this thread must wait until the exclusive lock is released

shared_mutex usage

std::shared_mutex shmut;

void write() {
  std::lock_guard lck_guard(shmut); // Write thread with exclusive lock
}

void read() {
  std::shared_lock sh_lck(shmut); // Read thread with shared lock
}

In this code, the readers were able to sleep concurrently.


#include <shared_mutex>
#include <thread>
#include <vector>

std::shared_mutex shmut;

int x = 0;

void write() {
  std::lock_guard<std::shared_mutex> lck_guard(shmut);
  ++x;
}

void read() {
  std::shared_lock<std::shared_mutex> lck_guard(shmut);

  using namespace std::literals;
  std::this_thread::sleep_for(100ms);
}

int main() {
  std::vector<std::thread> threads;

  for (int i = 0; i < 20; ++i) {
    threads.push_back(std::thread(read));
  }

  threads.push_back(std::thread(write));
  threads.push_back(std::thread(write));

  for (int i = 0; i < 20; ++i) {
    threads.push_back(std::thread(read));
  }

  for (auto &thr : threads) {
    thr.join();
  }
}

std::shared_mutex Member Functions

  • Exclusive locking

    • lock()
    • try_lock()
    • unlock()
  • Shared locking

    • lock_shared()
    • try_lock_shared()
    • unlock_shared()

Data Race Avoidance

  • The writer thread cannot get an exclusive lock

    • Until all other threads release their locks
    • Those threads have now left their critical sections
  • The writer thread acquires an exclusive lock

    • It enters the critical section
    • Reader threads cannot get a shared lock
    • Writer threads cannot get an exclusive lock
    • Until this thread releases its lock
  • The writer thread releases its exclusive lock

    • It has now left its critical section
  • The reader thread cannot get a shared lock

    • Until a writer thread releases its exclusive lock
    • The writer thread has now left its critical section
  • The reader thread acquires a shared lock

    • It enters the critical section
    • Other reader threads can also get a shared lock
  • There is no scenario in which there is a data race

    • Reader and writer threads cannot interleave in a critical section

Pros and Cons of std::shared_mutex

  • Uses more memory than std::mutex
  • Slower than std::mutex
  • Best suited to situations where
    • Reader threads greatly outnumber writer threads
    • Read operations take a long time