Timeouts and Mutexes

Multiple Locking

  • A thread locks an std::mutex
  • It must not call lock() again until it has called unlock()
    • Undefined Behavior
    • Usually the program blocks indefinitely

std::recursive_mutex

  • Its lock() member can be called repeatedly

    • Without calling unlock() first
    • For each lock() call, there must eventually be an unlock() call
  • Normally a sign of bad design!

  • Some bad example:


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

std::recursive_mutex rmut;

int bad_factorial(int n) {
  if (n <= 1) {
    std::cout << "returning " << 1 << '\n';
    return 1;
  }

  std::lock_guard<std::recursive_mutex> lck_guard(rmut);

  int retval = n * bad_factorial(n - 1);
  std::cout << "returning " << retval << std::endl;

  return retval;
}

int main() {
  std::thread thr1(bad_factorial, 8);
  std::thread thr2(bad_factorial, 11);

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

std::timed_mutex

  • Similar to std::mutex, but with extra member functions

    • try_lock_for()
      • Keep trying to lock the mutex for a specified duration
    • try_lock_until()
      • Keep trying to lock the mutex until a specified time
  • These return bool

    • True if the mutex was locked
    • Otherwise false

Other Mutexes with Timeouts

  • There is an std::recursive_timed_mutex
  • Has the same member functions
    • lock()
    • try_lock()
    • try_lock_for()
    • try_lock_until()

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

using namespace std::literals;

std::timed_mutex the_mutex;

void task1() {
  std::cout << "Task1 trying to lock the mutex\n";
  the_mutex.lock();
  std::cout << "Task1 locks the mutex\n";
  std::this_thread::sleep_for(5s);
  std::cout << "Task1 unlocking the mutex\n";
  the_mutex.unlock();
}

void task2() {
  std::this_thread::sleep_for(500ms);
  std::cout << "Task2 trying to lock the mutex\n";

  // Try for 1 second to lock the mutex
  while (!the_mutex.try_lock_for(1s)) {
    std::cout << "Task2 could not lock the mutex\n";
  }
  // Return true - the mutex is now locked

  // Start of critical section
  std::cout << "Task2 has locked the mutex\n";
  // End of critical section

  the_mutex.unlock();
}

int main() {
  std::thread thr1(task1);
  std::thread thr2(task2);

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

void task3() {
  std::this_thread::sleep_for(500ms);
  std::cout << "Task3 trying to lock the mutex\n";
  auto deadline = std::chrono::system_clock::now() + 900ms;

  // Try for 1 second to lock the mutex
  while (!the_mutex.try_lock_until(deadline)) {
    // Returned false
    // Update "deadline" and try again
    deadline = std::chrono::system_clock::now() + 900ms;
    std::cout << "Task3 could not lock the mutex\n";
  }
  // Return true - the mutex is now locked

  // Start of critical section
  std::cout << "Task3 has locked the mutex\n";
  // End of critical section

  the_mutex.unlock();
}

std::unique_lock

  • std::unique_lock has member functions

    • try_lock_for()
    • try_lock_until()
  • These are forwarded to the wrapped mutex

    • Will only compile if the mutex supports the operation

std::timed_mutex the_mutex;

void task1() {
  std::cout << "Task 1 trying to lock the mutex\n";
  std::lock_guard<std::timed_mutex> lck_guard(the_mutex);
  std::cout << "Task 1 locks the mutex\n";
  std::this_thread::sleep_for(5s);
  std::cout << "Task 1 unlocking the mutex\n";
}

void task2() {
  std::this_thread::sleep_for(500ms);
  std::cout << "Task 2 trying to lock the mutex\n";

  std::unique_lock<std::timed_mutex> uniq_lck(the_mutex, std::defer_lock);

  // Try for 1 second to lock the mutex
  while (!uniq_lck.try_lock_for(1s)) {
    // Returned false
    std::cout << "Task 2 could not lock the mutex\n";

    // Try again for next iteration
  }

  // Returned true - the mutex is now locked

  // start of critical section
  std::cout << "Task 2 has locked the mutex\n";
  // End of critical section
}

std::chrono Clocks

  • chrono::system_clock

    • Gets time from operating system
    • May change erratically
    • Use it for time points
  • chrono::steady_clock

    • Always increases steadily
    • Use it for measuring intervals
  • try_lock_for() and try_lock_until() may return later than requested

    • Due to scheduling issues