Compare commits

..

No commits in common. "097af8d222a74adea1e9373c0304fc4151c1a173" and "a97dfbe34b96ad55762896f97c94170d09433503" have entirely different histories.

15 changed files with 15 additions and 337 deletions

View File

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

View File

@ -15,9 +15,9 @@
void BubbleSort(std::vector<int> &array) void BubbleSort(std::vector<int> &array)
{ {
// For each value within the set, starting at 0 // For each value within the set, starting at 0
for (size_t sortedPivot = 0; sortedPivot < array.size(); sortedPivot++) { for (int sortedPivot = 0; sortedPivot < array.size(); sortedPivot++) {
// Check every other remaining value in the set // Check every other remaining value in the set
for (size_t j = array.size() - 1; j > sortedPivot; j--) { for (int j = array.size() - 1; j > sortedPivot; j--) {
// Swap if the value at j is less than the value before it // Swap if the value at j is less than the value before it
if (array[j] < array[j - 1]) { if (array[j] < array[j - 1]) {
std::swap(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 // 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 // + Since each element stores its own count, just add the count at index i-1
for (int32_t i = 1; i <= maxValue; i++) { for (size_t i = 1; i <= maxValue; i++) {
tempArray[i] += tempArray[i - 1]; tempArray[i] += tempArray[i - 1];
// tempArray[i] - 1 now represents the sorted 0-index pos for each value i // 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 Left(const size_t &index) { return 2 * index + 1;}
size_t Right(const size_t &index) { return (2 * index) + 2;} size_t Right(const size_t &index) { return (2 * index) + 2;}
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const size_t &heapSize) void MaxHeapify(std::vector<int> &array, size_t thisIndex, const int &heapSize)
{ {
// Get an index for the left and right nodes attached to thisIndex // Get an index for the left and right nodes attached to thisIndex
size_t l = Left(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 Left(const size_t &index);
size_t Right(const size_t &index); size_t Right(const size_t &index);
void MaxHeapify(std::vector<int> &array, size_t thisIndex, const size_t &heapSize); void MaxHeapify(std::vector<int> &array, size_t thisIndex, const int &heapSize);
void BuildMaxHeap(std::vector<int> &array); 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 // For each value, move left until we find sortedPosition for keyValue
// + Starting with keyValue at array[1], to check sortedPosition at array[0] // + Starting with keyValue at array[1], to check sortedPosition at array[0]
for (size_t keyIndex = 1; keyIndex <= array.size(); keyIndex++) { for (int keyIndex = 1; keyIndex <= array.size(); keyIndex++) {
// Save the current key value // Save the current key value
// + We will look for the sorted position of this value // + We will look for the sorted position of this value
const int keyValue = array[keyIndex]; 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 // + Return this value when done, so we know where the lhs partition ends
ssize_t lhsIndex = begin - 1; ssize_t lhsIndex = begin - 1;
// For each value within this partition, check for values < keyValue // For each value within this partition, check for values < keyValue
for (size_t j = begin; j <= end - 1; j++) { for (int j = begin; j <= end - 1; j++) {
if (array[j] <= keyValue) { if (array[j] <= keyValue) {
// Swap all values < keyValue into the lhs portion of array // Swap all values < keyValue into the lhs portion of array
std::swap(array[++lhsIndex], array[j]); 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 // 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 // + Since each element stores its own count, just add the count at index i-1
for (size_t i = 1; i < tempArray.size(); i++) { for (int i = 1; i < tempArray.size(); i++) {
tempArray[i] = tempArray[i] + tempArray[i - 1]; tempArray[i] = tempArray[i] + tempArray[i - 1];
} }

View File

@ -12,10 +12,10 @@
#include <vector> #include <vector>
void SelectionSort(std::vector<int> &arr) { void SelectionSort(std::vector<int> &arr) {
for (size_t leftIndex = 0; leftIndex < arr.size(); leftIndex++) { for (int leftIndex = 0; leftIndex < arr.size(); leftIndex++) {
// Get the index for the minimum number in the unsorted set // Get the index for the minimum number in the unsorted set
size_t min = leftIndex; int min = leftIndex;
for (size_t i = leftIndex; i < arr.size(); i++) { for (int i = leftIndex; i < arr.size(); i++) {
// Check if value at i is smaller than value at min index // 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 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()); temp.erase(it, temp.end());
// Step through each character in lexicographic order // Step through each character in lexicographic order
for (size_t i = 0; i < temp.size(); i++) { for (int i = 0; i < temp.size(); i++) {
// Check each character in the keyWord for the current lexicographic char // Check each character in the keyWord for the current lexicographic char
for (size_t j = 0; j < keyWord_.size(); j++) { for (int j = 0; j < keyWord_.size(); j++) {
// If they are equal, push the index of the char in keyWord to orderVect // If they are equal, push the index of the char in keyWord to orderVect
if (keyWord_[j] == temp[i]) { if (keyWord_[j] == temp[i]) {
orderVect_.push_back(j); orderVect_.push_back(j);
@ -109,7 +109,7 @@ std::string Columnar::Decrypt(std::string message)
rows.resize(orderVect_.size()); rows.resize(orderVect_.size());
// Track the ending position after each substring is taken // Track the ending position after each substring is taken
int lastPos = 0; int lastPos = 0;
for (size_t i = 0; i < orderVect_.size(); i++) { for (int i = 0; i < orderVect_.size(); i++) {
// If we are assigning to any row < fullRows, it should have + 1 character // If we are assigning to any row < fullRows, it should have + 1 character
if (orderVect_[i] < fullRows) { if (orderVect_[i] < fullRows) {
rows[orderVect_[i]] = message.substr(lastPos, rowLength + 1); rows[orderVect_[i]] = message.substr(lastPos, rowLength + 1);

View File

@ -1,19 +0,0 @@
################################################################################
## 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

@ -1,26 +0,0 @@
################################################################################
## 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

@ -1,189 +0,0 @@
/*##############################################################################
## 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

@ -1,22 +0,0 @@
################################################################################
## 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

@ -1,64 +0,0 @@
/*##############################################################################
## 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;
}