Compare commits
2 Commits
6c0018c469
...
6986c73651
Author | SHA1 | Date |
---|---|---|
Shaun Reed | 6986c73651 | |
Shaun Reed | 92b3af7813 |
|
@ -15,5 +15,7 @@ project(
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(conditions)
|
||||||
add_subdirectory(deadlock)
|
add_subdirectory(deadlock)
|
||||||
|
add_subdirectory(livelock)
|
||||||
add_subdirectory(race-condition)
|
add_subdirectory(race-condition)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
################################################################################
|
||||||
|
## Author: Shaun Reed ##
|
||||||
|
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
|
||||||
|
## About: An example of condition_variables in multithreaded C++ ##
|
||||||
|
## ##
|
||||||
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
# std::scoped_lock requires C++17
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
add_compile_options("-Wall")
|
||||||
|
|
||||||
|
project(
|
||||||
|
#[[NAME]] ConditionVariables
|
||||||
|
VERSION 1.0
|
||||||
|
DESCRIPTION "Example of condition_variables in multithreaded C++"
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
multithread-conditions driver.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(multithread-conditions pthread)
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*##############################################################################
|
||||||
|
## Author: Shaun Reed ##
|
||||||
|
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
|
||||||
|
## About: An example of condition_variables in multithreaded C++ ##
|
||||||
|
## ##
|
||||||
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
|
################################################################################
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
static std::mutex mtx;
|
||||||
|
std::condition_variable cv;
|
||||||
|
bool processing = false;
|
||||||
|
|
||||||
|
// Starts a job that waits for kick-off from main
|
||||||
|
// + When job finishes, handoff result back to main via processing bool
|
||||||
|
void job(int32_t & shared) {
|
||||||
|
std::unique_lock uniqueLock(mtx);
|
||||||
|
cv.wait(uniqueLock, []()->bool {return processing;});
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_A: Initial value of shared = " << shared << std::endl;
|
||||||
|
while (shared < INT32_MAX) {
|
||||||
|
shared++;
|
||||||
|
}
|
||||||
|
// We're no longer processing data
|
||||||
|
processing = false;
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_A: Done working." << std::endl;
|
||||||
|
uniqueLock.unlock(); // Important! Unlock uniqueLock before we notify
|
||||||
|
// Notify main that we've finished, so it can proceed
|
||||||
|
cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, const char * argv[]) {
|
||||||
|
std::cout << "main() thread id: " << std::this_thread::get_id() << std::endl;
|
||||||
|
|
||||||
|
int32_t share = 0;
|
||||||
|
std::thread thread_A(job, std::ref(share));
|
||||||
|
|
||||||
|
mtx.lock();
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||||
|
share = INT32_MAX / 2;
|
||||||
|
processing = true;
|
||||||
|
mtx.unlock();
|
||||||
|
// Notify thread_A that its work can begin
|
||||||
|
cv.notify_one();
|
||||||
|
|
||||||
|
// Wait until thread_A finishes its work
|
||||||
|
std::unique_lock uniqueLock(mtx);
|
||||||
|
// Block execution until we are not processing
|
||||||
|
cv.wait(uniqueLock, []()->bool { return !processing;});
|
||||||
|
std::cout << std::this_thread::get_id() << " main(): final value of shared = "
|
||||||
|
<< share << std::endl;
|
||||||
|
thread_A.join();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
################################################################################
|
||||||
|
## Author: Shaun Reed ##
|
||||||
|
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
|
||||||
|
## About: An example and solution for livelocks in C++ ##
|
||||||
|
## ##
|
||||||
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
# std::scoped_lock requires C++17
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
add_compile_options("-Wall")
|
||||||
|
|
||||||
|
project(
|
||||||
|
#[[NAME]] LiveLock
|
||||||
|
VERSION 1.0
|
||||||
|
DESCRIPTION "Example and solution for livelocks in C++"
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
multithread-livelock driver.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(multithread-livelock pthread)
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*##############################################################################
|
||||||
|
## Author: Shaun Reed ##
|
||||||
|
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
|
||||||
|
## About: An example and solution for livelocks in C++ ##
|
||||||
|
## ##
|
||||||
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
|
################################################################################
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
static std::mutex mtx_A, mtx_B, output;
|
||||||
|
|
||||||
|
// Helper function to output thread ID and string associated with mutex name
|
||||||
|
// + This must also be thread-safe, since we want threads to produce output
|
||||||
|
// + There is no bug or issue here; This is just in support of example output
|
||||||
|
void print_safe(const std::string & s) {
|
||||||
|
std::scoped_lock<std::mutex> scopedLock(output);
|
||||||
|
std::cout << s << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void problem() {
|
||||||
|
// Construct a vector with 5 agreed-upon times to synchronize loops in threads
|
||||||
|
typedef std::chrono::time_point<std::chrono::steady_clock,
|
||||||
|
std::chrono::steady_clock::duration> time_point;
|
||||||
|
std::vector<time_point> waitTime(6);
|
||||||
|
for (uint8_t i = 0; i < 6; i++) {
|
||||||
|
waitTime[i] = std::chrono::steady_clock::now()+std::chrono::seconds(1+i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread thread_A([waitTime]()->void {
|
||||||
|
uint8_t count = 0; // Used to select time slot from waitTime vector
|
||||||
|
bool done = false;
|
||||||
|
while (!done) {
|
||||||
|
count++;
|
||||||
|
std::lock_guard l(mtx_A);
|
||||||
|
std::cout << std::this_thread::get_id() << " thread_A: Lock A\n";
|
||||||
|
// Wait until the next time slot to continue
|
||||||
|
// + Helps to show example of livelock by ensuring B is not available
|
||||||
|
std::this_thread::sleep_until(waitTime[count]);
|
||||||
|
std::cout << std::this_thread::get_id() << " thread_A: Requesting B\n";
|
||||||
|
if (mtx_B.try_lock()) {
|
||||||
|
done = true;
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_A: Acquired locks for A, B! Done.\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_A: Can't lock B, unlocking A\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtx_B.unlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread thread_B([waitTime]()->void {
|
||||||
|
// As an example, enter livelock for only 5 iterations
|
||||||
|
// + Also used to select time slot from waitTime vector
|
||||||
|
uint8_t count = 0;
|
||||||
|
bool done = false;
|
||||||
|
while (!done && count < 5) {
|
||||||
|
count++;
|
||||||
|
std::lock_guard l(mtx_B);
|
||||||
|
// Wait until the next time slot to continue
|
||||||
|
// + Helps to show example of livelock by ensuring A is not available
|
||||||
|
std::this_thread::sleep_until(waitTime[count]);
|
||||||
|
if (mtx_A.try_lock()) {
|
||||||
|
// The program will never reach this point in the code
|
||||||
|
// + The only reason livelock ends is because count > 5
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread_A.join();
|
||||||
|
thread_B.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The solution below uses std::scoped_lock to avoid the livelock problem
|
||||||
|
void solution() {
|
||||||
|
std::thread thread_A([]()->void {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
// Increase wait time with i
|
||||||
|
// + To encourage alternating lock ownership between threads
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
|
||||||
|
std::scoped_lock l(mtx_A, mtx_B);
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_A: Acquired locks for A, B!" << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread thread_B([]()->void {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
|
||||||
|
std::scoped_lock l(mtx_B, mtx_A);
|
||||||
|
std::cout << std::this_thread::get_id()
|
||||||
|
<< " thread_B: Acquired locks for B, A!" << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread_A.join();
|
||||||
|
thread_B.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, const char * argv[]) {
|
||||||
|
std::cout << "main() thread id: " << std::this_thread::get_id() << std::endl;
|
||||||
|
|
||||||
|
problem();
|
||||||
|
|
||||||
|
std::cout << "\nSolution:\n\n";
|
||||||
|
solution();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue