std::unique_lock

  • The same basic features as std::lock_guard

    • Mutex data member
    • Constructor locks the mutex
    • Destructor unlocks it
  • It also has an unlock() member function

    • We can call this after the critical section
    • Avoids blocking other threads while we execute non-critical code
  • If we do not call unlock(), the destructor will unlock the mutex

    • The lock is always released

#include <iostream>
#include <mutex>
#include <string>
#include <thread>

using namespace std::literals;

std::mutex print_mutex;

void task(std::string str) {
  for (int i = 0; i < 5; ++i) {
    // Creat an std::unique_lock object
    // This calls print_mutex.lock();
    std::unique_lock<std::mutex> uniq_lck(print_mutex);

    // Critical section begin
    std::cout << str[0] << str[1] << str[2] << std::endl;
    // Critical section end

    uniq_lck.unlock();

    std::this_thread::sleep_for(50ms);
  } // Calls ~std::unique_lock
}

int main() {
  std::thread thr1(task, "abc");
  std::thread thr2(task, "def");
  std::thread thr3(task, "wyz");

  thr1.join();
  thr2.join();
  thr3.join();
}

std::unique_lock Constructor Options

  • The default

    • Call the mutex's lock() member function
  • Try to get the lock

    • Do not wait if unsuccessful
    • (Timed mutex) Wait with a time-out if unsuccessful
  • Do not lock the mutex

    • It will be locked later
    • Or the mutex is already locked

std::unique_lock Constructor Optional Second Argument

  • std::try_to_lock

    • Calls the mutex's try_lock() member function
    • The owns_lock() member function checks if the mutex is locked
  • std::defer_lock

    • Does not lock the mutex
    • Can lock it later by calling the lock() member function
    • Or by passing the std::unique_lock object to std::lock()
  • std::adopt_lock

    • Takes a mutex that is already locked
    • Avoids locking the mutex twice

std::unique_lock and Move Semantics

  • A std::unique_lock object cannot be copied

  • It can be moved

    • The lock is transferred to another std::unique_lock object
    • Can only be done within the same thread
  • We can write a function that creates a lock object and returns it

    • The function could lock different types of mutex, depending on its arguments
    • Factory design pattern

std::unique_lock vs std::lock_guard

  • std::unique_lock is much more flexible, but

    • Requires slightly more storage
    • Is slightly slower
  • Recommendations:

    • Use lock_guard to lock a mutex for an entire scope
    • Use unique_lock if you need to unlock within the scope
    • Use unique_lock if you need the extra features