From 37af8fc398d7feb11b6a49a23c236df7ac85cb69 Mon Sep 17 00:00:00 2001 From: Bassem Girgis Date: Sat, 20 Oct 2018 12:26:51 -0500 Subject: [PATCH] Add C++ lock examples --- Makefile | 2 + src/ch03/backoff_cpp.cpp | 194 +++++++++++++++++++++++++++++++++++++++ src/ch03/trylock_cpp.cpp | 79 ++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 src/ch03/backoff_cpp.cpp create mode 100644 src/ch03/trylock_cpp.cpp diff --git a/Makefile b/Makefile index 76c896b..5b6172e 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,8 @@ ch01/alarm_fork_cpp.cpp \ ch01/alarm_thread_cpp.cpp \ ch02/hello_cpp.cpp \ ch02/lifecycle_cpp.cpp \ +ch03/backoff_cpp.cpp \ +ch03/trylock_cpp.cpp \ ch03/alarm_mutex_cpp.cpp \ ch04/pipe_cpp.cpp diff --git a/src/ch03/backoff_cpp.cpp b/src/ch03/backoff_cpp.cpp new file mode 100644 index 0000000..8c10f77 --- /dev/null +++ b/src/ch03/backoff_cpp.cpp @@ -0,0 +1,194 @@ + +#include "errors.hpp" + +#include +#include +#include +#include + +#define ITERATIONS 10 + +/* + * Initialize a static array of 3 mutexes. + */ +std::mutex mutex[3]; + +int backoff = 1; /* Whether to backoff or deadlock */ +int yield_flag = 0; /* 0: no yield, >0: yield, <0: sleep */ + +/* + * This is a thread start routine that locks all mutexes in + * order, to ensure a conflict with lock_reverse, which does the + * opposite. + */ + +void +lock_forward_new() +{ + for (int iterate = 0; iterate < ITERATIONS; iterate++) { + std::lock(mutex[0], mutex[1], mutex[2]); + std::lock_guard l0(mutex[0], std::adopt_lock); + std::lock_guard l1(mutex[1], std::adopt_lock); + std::lock_guard l2(mutex[2], std::adopt_lock); + std::cout << "new lock forward got all locks" << std::endl; + } +} + +void +lock_forward() +{ + for (int iterate = 0; iterate < ITERATIONS; iterate++) { + int backoffs = 0; + for (int i = 0; i < 3; i++) { + if (i == 0) { + mutex[i].lock(); + } + else { + bool isFree = true; + if (backoff) + isFree = mutex[i].try_lock(); + else + mutex[i].lock(); + if (!isFree) { + backoffs++; + std::cout << " [forward locker backing off at " << i << "]" + << std::endl; + for (; i >= 0; i--) { + mutex[i].unlock(); + } + } + else { + std::cout << " forward locker got " << i << std::endl; + } + } + /* + * Yield processor, if needed to be sure locks get + * interleaved on a uniprocessor. + */ + if (yield_flag) { + if (yield_flag > 0) + std::this_thread::yield(); + else + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + /* + * Report that we got 'em, and unlock to try again. + */ + std::cout << "lock forward got all locks, " << backoffs << " backoffs" + << std::endl; + mutex[2].unlock(); + mutex[1].unlock(); + mutex[0].unlock(); + std::this_thread::yield(); + } +} + +void +lock_backward_new() +{ + for (int iterate = 0; iterate < ITERATIONS; iterate++) { + std::lock(mutex[2], mutex[1], mutex[0]); + std::lock_guard l2(mutex[2], std::adopt_lock); + std::lock_guard l1(mutex[1], std::adopt_lock); + std::lock_guard l0(mutex[0], std::adopt_lock); + std::cout << "new lock backward got all locks" << std::endl; + } +} + +/* + * This is a thread start routine that locks all mutexes in + * reverse order, to ensure a conflict with lock_forward, which + * does the opposite. + */ +void +lock_backward() +{ + for (int iterate = 0; iterate < ITERATIONS; iterate++) { + int backoffs = 0; + for (int i = 2; i >= 0; i--) { + if (i == 2) { + mutex[i].lock(); + } + else { + bool isFree = true; + if (backoff) + isFree = mutex[i].try_lock(); + else + mutex[i].lock(); + if (!isFree) { + backoffs++; + std::cout << " [backward locker backing off at " << i << "]" + << std::endl; + for (; i < 3; i++) { + mutex[i].unlock(); + } + } + else { + std::cout << " backward locker got " << i << std::endl; + } + } + /* + * Yield processor, if needed to be sure locks get + * interleaved on a uniprocessor. + */ + if (yield_flag) { + if (yield_flag > 0) + std::this_thread::yield(); + else + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + /* + * Report that we got 'em, and unlock to try again. + */ + std::cout << "lock backward got all locks, " << backoffs << " backoffs" + << std::endl; + mutex[0].unlock(); + mutex[1].unlock(); + mutex[2].unlock(); + std::this_thread::yield(); + } +} + +int +main(int argc, char* argv[]) +{ + /* + * If the first argument is absent, or nonzero, a backoff + * algorithm will be used to avoid deadlock. If the first + * argument is zero, the program will deadlock on a lock + * "collision." + */ + if (argc > 1) + backoff = atoi(argv[1]); + + /* + * If the second argument is absent, or zero, the two + * threads run "at speed." On some systems, especially + * uniprocessors, one thread may complete before the other + * has a chance to run, and you won't see a deadlock or + * backoffs. In that case, try running with the argument set + * to a positive number to cause the threads to call + * sched_yield() at each lock; or, to make it even more + * obvious, set to a negative number to cause the threads to + * call sleep(1) instead. + */ + if (argc > 2) + yield_flag = atoi(argv[2]); + + if (backoff < 0) { + std::thread t1(lock_forward_new); + std::thread t2(lock_backward_new); + + t1.join(); + t2.join(); + return 0; + } + + std::thread t1(lock_forward); + std::thread t2(lock_backward); + + t1.join(); + t2.join(); +} diff --git a/src/ch03/trylock_cpp.cpp b/src/ch03/trylock_cpp.cpp new file mode 100644 index 0000000..00ed47d --- /dev/null +++ b/src/ch03/trylock_cpp.cpp @@ -0,0 +1,79 @@ + +#include "errors.hpp" + +#include +#include +#include +#include + +#define SPIN 10000000 + +using ClockType = std::chrono::high_resolution_clock; + +std::mutex mutex; +long counter; +ClockType::time_point end_time; + +/* + * Thread start routine that repeatedly locks a mutex and + * increments a counter. + */ +void +counter_thread() +{ + /* + * Until end_time, increment the counter each + * second. Instead of just incrementing the counter, it + * sleeps for another second with the mutex locked, to give + * monitor_thread a reasonable chance of running. + */ + while (ClockType::now() < end_time) { + { + std::lock_guard l(mutex); + for (long spin = 0; spin < SPIN; spin++) counter++; + // std::this_thread::sleep_for(std::chrono::seconds(4)); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + std::cout << "[Counter] total spins " << counter << std::endl; +} + +/* + * Thread start routine to "monitor" the counter. Every 3 + * seconds, try to lock the mutex and read the counter. If the + * trylock fails, skip this cycle. + */ +void +monitor_thread() +{ + /* + * Loop until end_time, checking the counter every 3 + * seconds. + */ + int misses = 0; + while (ClockType::now() < end_time) { + std::this_thread::sleep_for(std::chrono::seconds(3)); + if (mutex.try_lock()) { + std::lock_guard l(mutex, std::adopt_lock); + std::cout << "[Monitor] Counter is " << counter / SPIN << std::endl; + } + else + misses++; /* Count "misses" on the lock */ + } + + std::cout << "[Monitor] thread missed update " << misses << " times.\n"; +} + +int +main(int argc, char* argv[]) +{ + end_time = ClockType::now() + std::chrono::seconds(60); /* Run for 1 minute */ + std::thread t1(counter_thread); + std::thread t2(monitor_thread); + + t1.join(); + t2.join(); + + return 0; +}