Timeouts and Mutexes
Multiple Locking
- A thread locks an
std::mutex - It must not call
lock()again until it has calledunlock()- 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 anunlock()call
- Without calling
-
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 functionstry_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_lockhas member functionstry_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()andtry_lock_until()may return later than requested- Due to scheduling issues