Compare commits
3 Commits
a97dfbe34b
...
097af8d222
Author | SHA1 | Date |
---|---|---|
Shaun Reed | 097af8d222 | |
Shaun Reed | d81c65b1d2 | |
Shaun Reed | fc1f247987 |
|
@ -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)
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue