Add C++ lock examples

This commit is contained in:
Bassem Girgis
2018-10-20 12:26:51 -05:00
parent 3005349ac0
commit 37af8fc398
3 changed files with 275 additions and 0 deletions

View File

@@ -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

194
src/ch03/backoff_cpp.cpp Normal file
View File

@@ -0,0 +1,194 @@
#include "errors.hpp"
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#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<std::mutex> l0(mutex[0], std::adopt_lock);
std::lock_guard<std::mutex> l1(mutex[1], std::adopt_lock);
std::lock_guard<std::mutex> 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<std::mutex> l2(mutex[2], std::adopt_lock);
std::lock_guard<std::mutex> l1(mutex[1], std::adopt_lock);
std::lock_guard<std::mutex> 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();
}

79
src/ch03/trylock_cpp.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "errors.hpp"
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#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<std::mutex> 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<std::mutex> 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;
}