Compare commits

...

3 Commits

Author SHA1 Message Date
Shaun Reed 097af8d222 [cpp] Add example and solution for deadlocks 2022-04-02 11:44:00 -04:00
Shaun Reed d81c65b1d2 [cpp] Add multithreaded project
+ Add example for race condition problem / solution
2022-04-02 11:40:58 -04:00
Shaun Reed fc1f247987 [cpp] Add -Wall compiler option to root CMakeLists
+ Resolve all warnings
2022-03-31 17:42:23 -04:00
15 changed files with 337 additions and 15 deletions

View File

@ -18,10 +18,12 @@ project(
)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_compile_options("-Wall")
add_subdirectory(algorithms)
add_subdirectory(cmake-example)
add_subdirectory(cryptography)
add_subdirectory(datastructs)
add_subdirectory(graphics)
add_subdirectory(patterns)
add_subdirectory(multithreading)
add_subdirectory(patterns)

View File

@ -15,9 +15,9 @@
void BubbleSort(std::vector<int> &array)
{
// For each value within the set, starting at 0
for (int sortedPivot = 0; sortedPivot < array.size(); sortedPivot++) {
for (size_t sortedPivot = 0; sortedPivot < array.size(); sortedPivot++) {
// Check every other remaining value in the set
for (int j = array.size() - 1; j > sortedPivot; j--) {
for (size_t j = array.size() - 1; j > sortedPivot; j--) {
// Swap if the value at j is less than the value before it
if (array[j] < array[j - 1]) {
std::swap(array[j], array[j - 1]);

View File

@ -33,7 +33,7 @@ void CountingSort(std::vector<int> &array)
// Count the values less than or equal to each element of tempArray
// + Since each element stores its own count, just add the count at index i-1
for (size_t i = 1; i <= maxValue; i++) {
for (int32_t i = 1; i <= maxValue; i++) {
tempArray[i] += tempArray[i - 1];
// tempArray[i] - 1 now represents the sorted 0-index pos for each value i
}

View File

@ -17,7 +17,7 @@ size_t Parent(const size_t &index) { return index / 2;}
size_t Left(const size_t &index) { return 2 * index + 1;}
size_t Right(const size_t &index) { return (2 * index) + 2;}
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const int &heapSize)
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const size_t &heapSize)
{
// Get an index for the left and right nodes attached to thisIndex
size_t l = Left(thisIndex);

View File

@ -18,7 +18,7 @@ size_t Parent(const size_t &index);
size_t Left(const size_t &index);
size_t Right(const size_t &index);
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const int &heapSize);
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const size_t &heapSize);
void BuildMaxHeap(std::vector<int> &array);

View File

@ -15,7 +15,7 @@ void InsertionSort(std::vector<int> &array)
{
// For each value, move left until we find sortedPosition for keyValue
// + Starting with keyValue at array[1], to check sortedPosition at array[0]
for (int keyIndex = 1; keyIndex <= array.size(); keyIndex++) {
for (size_t keyIndex = 1; keyIndex <= array.size(); keyIndex++) {
// Save the current key value
// + We will look for the sorted position of this value
const int keyValue = array[keyIndex];

View File

@ -50,7 +50,7 @@ size_t Partition(std::vector<int> &array, size_t begin, size_t end)
// + Return this value when done, so we know where the lhs partition ends
ssize_t lhsIndex = begin - 1;
// For each value within this partition, check for values < keyValue
for (int j = begin; j <= end - 1; j++) {
for (size_t j = begin; j <= end - 1; j++) {
if (array[j] <= keyValue) {
// Swap all values < keyValue into the lhs portion of array
std::swap(array[++lhsIndex], array[j]);

View File

@ -41,7 +41,7 @@ void CountingSort(std::vector<int> &array, int placeValue)
// Count the values less than or equal to each element of tempArray
// + Since each element stores its own count, just add the count at index i-1
for (int i = 1; i < tempArray.size(); i++) {
for (size_t i = 1; i < tempArray.size(); i++) {
tempArray[i] = tempArray[i] + tempArray[i - 1];
}

View File

@ -12,10 +12,10 @@
#include <vector>
void SelectionSort(std::vector<int> &arr) {
for (int leftIndex = 0; leftIndex < arr.size(); leftIndex++) {
for (size_t leftIndex = 0; leftIndex < arr.size(); leftIndex++) {
// Get the index for the minimum number in the unsorted set
int min = leftIndex;
for (int i = leftIndex; i < arr.size(); i++) {
size_t min = leftIndex;
for (size_t i = leftIndex; i < arr.size(); i++) {
// Check if value at i is smaller than value at min index
min = (arr[min] > arr[i]) ? i : min; // Update min value to i if true
}

View File

@ -21,9 +21,9 @@ void Columnar::InitOrder(std::string temp)
temp.erase(it, temp.end());
// Step through each character in lexicographic order
for (int i = 0; i < temp.size(); i++) {
for (size_t i = 0; i < temp.size(); i++) {
// Check each character in the keyWord for the current lexicographic char
for (int j = 0; j < keyWord_.size(); j++) {
for (size_t j = 0; j < keyWord_.size(); j++) {
// If they are equal, push the index of the char in keyWord to orderVect
if (keyWord_[j] == temp[i]) {
orderVect_.push_back(j);
@ -109,7 +109,7 @@ std::string Columnar::Decrypt(std::string message)
rows.resize(orderVect_.size());
// Track the ending position after each substring is taken
int lastPos = 0;
for (int i = 0; i < orderVect_.size(); i++) {
for (size_t i = 0; i < orderVect_.size(); i++) {
// If we are assigning to any row < fullRows, it should have + 1 character
if (orderVect_[i] < fullRows) {
rows[orderVect_[i]] = message.substr(lastPos, rowLength + 1);

View File

@ -0,0 +1,19 @@
################################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: A root project for practicing C++ multithreading ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
################################################################################
cmake_minimum_required(VERSION 3.16)
project(
#[[NAME]] Multithreading
VERSION 1.0
DESCRIPTION "Practice with multithreaded programming in C++"
LANGUAGES CXX
)
add_subdirectory(deadlock)
add_subdirectory(race-condition)

View File

@ -0,0 +1,26 @@
################################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: An example and solution for deadlocks 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]] Deadlock
VERSION 1.0
DESCRIPTION "Example and solution for deadlocks in C++"
LANGUAGES CXX
)
add_executable(
multithread-deadlock driver.cpp
)
target_link_libraries(multithread-deadlock pthread)

View File

@ -0,0 +1,189 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: An example and solution for deadlocks in C++ ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
################################################################################
*/
#include <chrono>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
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;
}
// Helper function to convert std::thread::id to string
std::string id_string(const std::thread::id & id) {
std::stringstream stream;
stream << id;
return stream.str();
}
// In the two threads within this function, we have a problem
// + The mutex locks are acquired in reverse order, so they collide
// + This is called a deadlock; The program will *never* finish
void problem() {
std::thread thread_A([]()->void {
mtx_A.lock();
print_safe(id_string(std::this_thread::get_id()) + " thread_A: Locked A");
std::this_thread::sleep_for(std::chrono::seconds(1));
mtx_B.lock(); // We can't lock B! thread_B is using it
// The program will never reach this point in execution; We are in deadlock
print_safe(id_string(std::this_thread::get_id())
+ " thread_A: B has been unlocked, we can proceed!\n Locked B"
);
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id())
+ " thread_A: Unlocking A, B..."
);
mtx_A.unlock();
mtx_B.unlock();
});
std::thread thread_B([]()->void {
mtx_B.lock();
print_safe(id_string(std::this_thread::get_id()) + " thread_B: Locked B");
std::this_thread::sleep_for(std::chrono::seconds(1));
mtx_A.lock(); // We can't lock A! thread_A is using it
// The program will never reach this point in execution; We are in deadlock
print_safe(id_string(std::this_thread::get_id())
+ " thread_B: A has been unlocked, we can proceed!\n Locked A"
);
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id())
+ " thread_B: Unlocking B, A..."
);
mtx_B.unlock();
mtx_A.unlock();
});
// This offers a way out of the deadlock, so we can proceed to the solution
std::this_thread::sleep_for(std::chrono::seconds(2));
char input;
print_safe("\n"
+ id_string(std::this_thread::get_id())
+ " problem(): We are in a deadlock. \n"
+ " Enter y/Y to continue to the solution...\n"
);
while (std::cin >> input) {
if (input != 'Y' && input != 'y') continue;
else break;
}
print_safe(id_string(std::this_thread::get_id())
+ " problem(): Unlocking A, B..."
);
mtx_A.unlock();
mtx_B.unlock();
thread_A.join();
thread_B.join();
}
// std::lock will lock N mutex locks
// + If either is in use, execution will block until both are available to lock
void solution_A() {
std::thread thread_A([]()->void {
std::lock(mtx_A, mtx_B);
print_safe(id_string(std::this_thread::get_id()) + ": Locked A, B");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking A, B...");
mtx_A.unlock();
mtx_B.unlock();
});
std::thread thread_B([]()->void {
std::lock(mtx_B, mtx_A);
print_safe(id_string(std::this_thread::get_id()) + ": Locked B, A");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking B, A...");
mtx_B.unlock();
mtx_A.unlock();
});
thread_A.join();
thread_B.join();
}
// std::lock_guard is a C++11 object which can be constructed with 1 mutex
// + When the program leaves the scope of the guard, the mutex is unlocked
void solution_B() {
std::thread thread_A([]()->void {
// lock_guard will handle unlocking when program leaves this scope
std::lock_guard<std::mutex> guard_A(mtx_A), guard_B(mtx_B);
print_safe(id_string(std::this_thread::get_id()) + ": Locked A, B");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking A, B...");
// We don't need to explicitly unlock either mutex
});
std::thread thread_B([]()->void {
std::lock_guard<std::mutex> guard_B(mtx_B), guard_A(mtx_A);
print_safe(id_string(std::this_thread::get_id()) + ": Locked B, A");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking B, A...");
// We don't need to explicitly unlock either mutex
});
thread_A.join();
thread_B.join();
}
// std::scoped_lock is a C++17 object that can be constructed with N mutex
// + When the program leaves this scope, all N mutex will be unlocked
void solution_C() {
std::thread thread_A([]()->void {
// scoped_lock will handle unlocking when program leaves this scope
std::scoped_lock scopedLock(mtx_A, mtx_B);
print_safe(id_string(std::this_thread::get_id()) + ": Locked A, B");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking A, B...");
// We don't need to explicitly unlock either mutex
});
std::thread thread_B([]()->void {
std::scoped_lock scopedLock(mtx_A, mtx_B);
print_safe(id_string(std::this_thread::get_id()) + ": Locked B, A");
std::this_thread::sleep_for(std::chrono::seconds(1));
print_safe(id_string(std::this_thread::get_id()) + ": Unlocking B, A...");
// We don't need to explicitly unlock either mutex
});
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();
print_safe("\nsolution_A, using std::lock\n");
solution_A();
print_safe("\nsolution_B, using std::lock_guard\n");
solution_B();
print_safe("\nsolution_C, using std::scoped_lock\n");
solution_C();
return 0;
}

View File

@ -0,0 +1,22 @@
################################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: An example and solution for race conditions in C++ ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
################################################################################
cmake_minimum_required(VERSION 3.16)
project(
#[[NAME]] RaceCondition
VERSION 1.0
DESCRIPTION "Example and solution for race conditions"
LANGUAGES CXX
)
add_executable(
multithread-race-condition driver.cpp
)
target_link_libraries(multithread-race-condition pthread)

View File

@ -0,0 +1,64 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: An example of a race condition problem and solution ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
################################################################################
*/
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
void problem() {
std::vector<std::thread> threads;
const uint8_t thread_count = 5;
// With no mutex lock, the final value will vary in the range 1000000-5000000
// + Threads will modify x simultaneously, so some iterations will be lost
// + x will have same initial value entering this loop on different threads
uint32_t x = 0;
for (uint8_t i = 0; i < thread_count; i++) {
threads.emplace_back([&x](){
for (uint32_t i = 0; i < 1000000; i++) {
x = x + 1;
};
});
}
// Ensure the function doesn't continue until all threads are finished
// + There's no issue here, the issue is in how `x` is accessed above
for (auto &thread : threads) thread.join();
std::cout << x << std::endl;
}
// Create mutex lock to prevent threads from modifying same value simultaneously
static std::mutex mtx;
void solution() {
std::vector<std::thread> threads;
const uint8_t thread_count = 5;
uint32_t x = 0;
for (uint8_t i = 0; i < thread_count; i++) {
threads.emplace_back([&x](){
// The first thread that arrives here will 'lock' other threads from passing
// + Once first thread finishes, the next thread will resume
// + This process repeats until all threads finish
std::lock_guard<std::mutex> lock(mtx);
for (uint32_t i = 0; i < 1000000; i++) {
x = x + 1;
};
});
}
// Ensure the function doesn't continue until all threads are finished
for (auto &thread : threads) thread.join();
std::cout << x << std::endl;
}
int main(const int argc, const char * argv[]) {
// Result will vary from 1000000-5000000
problem();
// Result will always be 5000000
solution();
return 0;
}