Add C++ lock examples
This commit is contained in:
194
src/ch03/backoff_cpp.cpp
Normal file
194
src/ch03/backoff_cpp.cpp
Normal 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
79
src/ch03/trylock_cpp.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user