├── ActiveObject ├── CMakeLists.txt ├── README.md └── activeObject.cpp ├── CopiedValue ├── CMakeLists.txt ├── README.md └── copiedValueDataRace.cpp ├── EventLoop ├── AccessSerialization │ ├── CMakeLists.txt │ ├── event_loop.hpp │ ├── i_bank_account.hpp │ ├── main.cpp │ ├── thread_safe_account.hpp │ └── thread_unsafe_account.hpp ├── Basic │ ├── CMakeLists.txt │ ├── event_loop.hpp │ └── main.cpp └── EventHandlers │ ├── CMakeLists.txt │ ├── event_loop.hpp │ └── main.cpp ├── Future ├── CMakeLists.txt ├── README.md └── future.cpp ├── GuardedSuspension ├── CMakeLists.txt ├── README.md └── bossWorker.cpp ├── LICENSE ├── LockFree ├── AtomicSharedPointerStack │ ├── CMakeLists.txt │ └── main.cpp ├── AtomicSmartPointerStack │ ├── CMakeLists.txt │ └── main.cpp ├── CompleteStack │ ├── CMakeLists.txt │ └── main.cpp ├── HazardPointerStack │ ├── CMakeLists.txt │ └── main.cpp ├── README.md └── SimpleStack │ ├── CMakeLists.txt │ └── main.cpp ├── MonitorObject ├── Cpp11 │ ├── CMakeLists.txt │ └── monitorObject.cpp ├── Cpp20 │ ├── CMakeLists.txt │ └── monitorObjectCpp20.cpp └── README.md ├── ProducerConsumer ├── README.md ├── first_readers_writers │ ├── CMakeLists.txt │ └── main.cpp ├── producer_consumer_bounded_buffer │ ├── CMakeLists.txt │ └── main.cpp ├── producer_consumer_channel │ ├── CMakeLists.txt │ ├── channel.hpp │ ├── channel.tpp │ ├── main.cpp │ ├── selector.cpp │ └── selector.hpp ├── producer_consumer_channel_2 │ ├── CMakeLists.txt │ ├── blocking_iterator.hpp │ ├── channel.hpp │ ├── channel.inl │ └── main.cpp ├── producer_consumer_monitor │ ├── CMakeLists.txt │ └── main.cpp ├── producer_consumer_single │ ├── CMakeLists.txt │ └── main.cpp ├── producer_consumer_single_c │ ├── CMakeLists.txt │ └── main.c ├── second_readers_writers │ ├── CMakeLists.txt │ └── main.cpp ├── second_readers_writers_c │ ├── CMakeLists.txt │ └── main.c ├── simplest_readers_writers │ ├── CMakeLists.txt │ └── main.cpp └── third_readers_writers │ ├── CMakeLists.txt │ └── main.cpp ├── README.md ├── Reactor ├── Cpp11 │ ├── CMakeLists.txt │ ├── README.md │ └── reactor.cpp ├── Cpp11queue │ ├── CMakeLists.txt │ ├── README.md │ └── reactor.cpp ├── Cpp98 │ ├── CMakeLists.txt │ ├── ClientHandler.cpp │ ├── ClientHandler.h │ ├── Dispatcher.cpp │ ├── Dispatcher.h │ ├── EpollPoller.cpp │ ├── EpollPoller.h │ ├── EventHandler.h │ ├── ListenHandler.cpp │ ├── ListenHandler.h │ ├── Poller.h │ ├── README.md │ ├── main.cpp │ └── main_client.cpp ├── Poco │ ├── CMakeLists.txt │ └── main.cpp ├── README.md └── reactor.svg ├── ScopedLocking ├── CMakeLists.txt ├── README.md └── scopedLock.cpp ├── SeqLock ├── README.md ├── seqlock_emulator │ ├── CMakeLists.txt │ ├── Seqlock.hpp │ ├── main.cpp │ └── test.cpp ├── seqlock_mutex_emulator │ ├── CMakeLists.txt │ ├── Seqlock.hpp │ ├── main.cpp │ └── test.cpp ├── seqlock_naive │ ├── CMakeLists.txt │ ├── Seqlock.hpp │ ├── main.cpp │ └── test.cpp ├── seqlock_posix_emulator │ ├── CMakeLists.txt │ ├── Seqlock.hpp │ ├── main.cpp │ └── test.cpp ├── seqlock_rigtorp │ ├── CMakeLists.txt │ ├── main.cpp │ ├── rigtorp │ │ └── Seqlock.hpp │ └── test.cpp └── seqlock_spinlock │ ├── CMakeLists.txt │ ├── Seqlock.hpp │ ├── SpinLock.cpp │ ├── SpinLock.hpp │ ├── main.cpp │ └── test.cpp ├── Spinlock ├── README.md ├── audio_spin_mutex │ ├── CMakeLists.txt │ └── main.cpp ├── benchmarks │ ├── CMakeLists.txt │ ├── README.md │ ├── conanfile.txt │ └── main.cpp ├── spinlock_atomic_bool │ ├── CMakeLists.txt │ └── main.cpp ├── spinlock_atomic_flag │ ├── CMakeLists.txt │ └── main.cpp ├── spinlock_atomic_flag_c │ ├── CMakeLists.txt │ └── main.c └── spinlock_posix_c │ ├── CMakeLists.txt │ └── main.c ├── StrategizedLocking ├── Concepts │ ├── CMakeLists.txt │ └── strategizedLockingCompileTimeWithConcepts.cpp ├── README.md └── Runtime │ ├── CMakeLists.txt │ └── strategizedLockingRuntime.cpp ├── ThreadSafeInterface ├── CMakeLists.txt ├── README.md └── threadSafeInterface.cpp └── ThreadSpecificStorage ├── CMakeLists.txt ├── README.md └── threadLocalSummation.cpp /ActiveObject/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ActiveObject) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | activeObject.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /ActiveObject/activeObject.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using std::async; 15 | using std::boolalpha; 16 | using std::cout; 17 | using std::deque; 18 | using std::distance; 19 | using std::find_if; 20 | using std::for_each; 21 | using std::future; 22 | using std::jthread; 23 | using std::lock_guard; 24 | using std::make_move_iterator; 25 | using std::make_pair; 26 | using std::move; 27 | using std::mt19937; 28 | using std::mutex; 29 | using std::packaged_task; 30 | using std::pair; 31 | using std::random_device; 32 | using std::sort; 33 | using std::uniform_int_distribution; 34 | using std::vector; 35 | 36 | class IsPrime { 37 | public: 38 | pair operator()(int i) { 39 | for (int j = 2; j * j <= i; ++j) { 40 | if (i % j == 0) return make_pair(false, i); 41 | } 42 | return make_pair(true, i); 43 | } 44 | }; 45 | 46 | class ActiveObject { 47 | public: 48 | future> enqueueTask(int i) { 49 | IsPrime isPrime; 50 | packaged_task(int)> newJob(isPrime); 51 | auto isPrimeFuture = newJob.get_future(); 52 | auto pair = make_pair(move(newJob), i); 53 | { 54 | lock_guard lockGuard(activationListMutex); 55 | activationList.push_back(move(pair)); 56 | } 57 | return isPrimeFuture; 58 | } 59 | 60 | void run() { 61 | vector allServants; 62 | // only one thread because the activation list is the bottleneck 63 | for (int n = 0; n < 1; ++n) { 64 | allServants.emplace_back([this] { 65 | while (!runNextTask()) 66 | ; 67 | }); 68 | } 69 | } 70 | 71 | private: 72 | bool runNextTask() { 73 | lock_guard lockGuard(activationListMutex); 74 | auto empty = activationList.empty(); 75 | if (!empty) { 76 | auto myTask = std::move(activationList.front()); 77 | activationList.pop_front(); 78 | myTask.first(myTask.second); 79 | } 80 | return empty; 81 | } 82 | 83 | deque(int)>, int>> activationList; 84 | mutex activationListMutex; 85 | }; 86 | 87 | vector getRandNumbers(int number) { 88 | random_device seed; 89 | mt19937 engine(seed()); 90 | uniform_int_distribution<> dist(1000000, 1000000000); 91 | vector numbers; 92 | for (long long i = 0; i < number; ++i) numbers.push_back(dist(engine)); 93 | return numbers; 94 | } 95 | 96 | future>>> getFutures(ActiveObject& activeObject, 97 | int numberPrimes) { 98 | return async([&activeObject, numberPrimes] { 99 | vector>> futures; 100 | auto randNumbers = getRandNumbers(numberPrimes); 101 | for (auto numb : randNumbers) { 102 | futures.push_back(activeObject.enqueueTask(numb)); 103 | } 104 | return futures; 105 | }); 106 | } 107 | 108 | int main() { 109 | cout << boolalpha << '\n'; 110 | 111 | ActiveObject activeObject; 112 | 113 | // a few clients enqueue work concurrently 114 | auto client1 = getFutures(activeObject, 1998); 115 | auto client2 = getFutures(activeObject, 2003); 116 | auto client3 = getFutures(activeObject, 2011); 117 | auto client4 = getFutures(activeObject, 2014); 118 | auto client5 = getFutures(activeObject, 2017); 119 | 120 | // give me the futures 121 | auto futures = client1.get(); 122 | auto futures2 = client2.get(); 123 | auto futures3 = client3.get(); 124 | auto futures4 = client4.get(); 125 | auto futures5 = client5.get(); 126 | 127 | // put all futures together 128 | futures.insert(futures.end(), make_move_iterator(futures2.begin()), 129 | make_move_iterator(futures2.end())); 130 | 131 | futures.insert(futures.end(), make_move_iterator(futures3.begin()), 132 | make_move_iterator(futures3.end())); 133 | 134 | futures.insert(futures.end(), make_move_iterator(futures4.begin()), 135 | make_move_iterator(futures4.end())); 136 | 137 | futures.insert(futures.end(), make_move_iterator(futures5.begin()), 138 | make_move_iterator(futures5.end())); 139 | 140 | // run the promises 141 | activeObject.run(); 142 | 143 | // get the results from the futures 144 | vector> futResults; 145 | futResults.reserve(futures.size()); 146 | for (auto& fut : futures) futResults.push_back(fut.get()); 147 | 148 | sort(futResults.begin(), futResults.end()); 149 | 150 | // separate the primes from the non-primes 151 | auto prIt = find_if(futResults.begin(), futResults.end(), 152 | [](pair pa) { return pa.first == true; }); 153 | 154 | cout << "Number primes: " << distance(prIt, futResults.end()) << '\n'; 155 | cout << "Primes:" << '\n'; 156 | for_each(prIt, futResults.end(), [](auto p) { cout << p.second << " "; }); 157 | 158 | cout << "\n\n"; 159 | 160 | cout << "Number no primes: " << distance(futResults.begin(), prIt) << '\n'; 161 | cout << "No primes:" << '\n'; 162 | for_each(futResults.begin(), prIt, [](auto p) { cout << p.second << " "; }); 163 | 164 | cout << '\n'; 165 | } 166 | -------------------------------------------------------------------------------- /CopiedValue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(CopiedValue) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | copiedValueDataRace.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /CopiedValue/README.md: -------------------------------------------------------------------------------- 1 | # Copied Value 2 | 3 | * Dealing with Sharing Pattern 4 | 5 | * There is no need to synchronize when a thread takes its 6 | arguments by copy and not by reference. 7 | * Data races or lifetime issues are not possible. 8 | -------------------------------------------------------------------------------- /CopiedValue/copiedValueDataRace.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | void byCopy(bool b) { 9 | std::this_thread::sleep_for(1ms); 10 | std::cout << "byCopy: " << b << '\n'; 11 | } 12 | 13 | void byReference(bool& b) { 14 | std::this_thread::sleep_for(1ms); 15 | std::cout << "byReference: " << b << '\n'; 16 | } 17 | 18 | void byConstReference(const bool& b) { 19 | std::this_thread::sleep_for(1ms); 20 | std::cout << "byConstReference: " << b << '\n'; 21 | } 22 | 23 | int main() { 24 | std::cout << std::boolalpha << '\n'; 25 | 26 | bool shared{false}; 27 | 28 | std::thread t1(byCopy, shared); 29 | std::thread t2(byReference, std::ref(shared)); 30 | std::thread t3(byConstReference, std::cref(shared)); 31 | 32 | shared = true; 33 | 34 | t1.join(); 35 | t2.join(); 36 | t3.join(); 37 | 38 | std::cout << '\n'; 39 | } 40 | -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(AccessSerialization) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | i_bank_account.hpp 10 | thread_unsafe_account.hpp 11 | thread_safe_account.hpp 12 | event_loop.hpp 13 | main.cpp 14 | ) 15 | 16 | set_target_properties( 17 | ${PROJECT_NAME} 18 | PROPERTIES 19 | CXX_STANDARD 20 20 | CXX_STANDARD_REQUIRED YES 21 | CXX_EXTENSIONS NO 22 | ) 23 | 24 | target_compile_options( 25 | ${PROJECT_NAME} 26 | PRIVATE 27 | $<$:/W4 /WX> 28 | $<$>:-Wall -Wextra -Wpedantic> 29 | ) 30 | 31 | target_link_libraries( 32 | ${PROJECT_NAME} 33 | PRIVATE 34 | Threads::Threads 35 | ) 36 | -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/event_loop.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class EventLoop 10 | { 11 | public: 12 | using callable_t = std::function; 13 | 14 | EventLoop() = default; 15 | EventLoop(const EventLoop&) = delete; 16 | EventLoop(EventLoop&&) noexcept = delete; 17 | ~EventLoop() noexcept 18 | { 19 | enqueue([this] 20 | { 21 | m_running = false; 22 | }); 23 | m_thread.join(); 24 | } 25 | 26 | EventLoop& operator= (const EventLoop&) = delete; 27 | EventLoop& operator= (EventLoop&&) noexcept = delete; 28 | 29 | void enqueue(callable_t&& callable) noexcept 30 | { 31 | { 32 | std::lock_guard guard(m_mutex); 33 | m_writeBuffer.emplace_back(std::move(callable)); 34 | } 35 | m_condVar.notify_one(); 36 | } 37 | 38 | template 39 | auto enqueueSync(Func&& callable, Args&& ...args) 40 | { 41 | if (std::this_thread::get_id() == m_thread.get_id()) 42 | { 43 | return std::invoke( 44 | std::forward(callable), 45 | std::forward(args)...); 46 | } 47 | 48 | using return_type = std::invoke_result_t; 49 | using packaged_task_type = 50 | std::packaged_task; 51 | 52 | packaged_task_type task(std::forward(callable)); 53 | 54 | enqueue([&] 55 | { 56 | task(std::forward(args)...); 57 | }); 58 | 59 | return task.get_future().get(); 60 | } 61 | 62 | template 63 | [[nodiscard]] auto enqueueAsync(Func&& callable, Args&& ...args) 64 | { 65 | using return_type = std::invoke_result_t; 66 | using packaged_task_type = std::packaged_task; 67 | 68 | auto taskPtr = std::make_shared(std::bind( 69 | std::forward(callable), std::forward(args)...)); 70 | 71 | enqueue(std::bind(&packaged_task_type::operator(), taskPtr)); 72 | 73 | return taskPtr->get_future(); 74 | } 75 | 76 | private: 77 | std::vector m_writeBuffer; 78 | std::mutex m_mutex; 79 | std::condition_variable m_condVar; 80 | bool m_running{ true }; 81 | std::thread m_thread{ &EventLoop::threadFunc, this }; 82 | 83 | void threadFunc() noexcept 84 | { 85 | std::vector readBuffer; 86 | 87 | while (m_running) 88 | { 89 | { 90 | std::unique_lock lock(m_mutex); 91 | m_condVar.wait(lock, [this] 92 | { 93 | return !m_writeBuffer.empty(); 94 | }); 95 | std::swap(readBuffer, m_writeBuffer); 96 | } 97 | 98 | for (callable_t& func : readBuffer) 99 | { 100 | func(); 101 | } 102 | 103 | readBuffer.clear(); 104 | } 105 | } 106 | }; -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/i_bank_account.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct IBankAccount 4 | { 5 | virtual ~IBankAccount() = default; 6 | virtual void pay(unsigned amount) noexcept = 0; 7 | virtual void acquire(unsigned amount) noexcept = 0; 8 | virtual long long balance() const noexcept = 0; 9 | }; -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "event_loop.hpp" 4 | #include "thread_safe_account.hpp" 5 | #include "thread_unsafe_account.hpp" 6 | 7 | int main() 8 | { 9 | auto eventLoop = std::make_shared(); 10 | auto bankAccount = std::make_shared(100'000); 11 | 12 | std::thread buy = std::thread([](std::unique_ptr account) 13 | { 14 | for (int i = 1; i <= 10; ++i) 15 | { 16 | account->pay(i); 17 | } 18 | }, std::make_unique(eventLoop, bankAccount)); 19 | 20 | std::thread sell = std::thread([](std::unique_ptr account) 21 | { 22 | for (int i = 1; i <= 10; ++i) 23 | { 24 | account->acquire(i); 25 | } 26 | }, std::make_unique(eventLoop, bankAccount)); 27 | 28 | buy.join(); 29 | sell.join(); 30 | 31 | std::cout << bankAccount->balance() << '\n'; 32 | } 33 | -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/thread_safe_account.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "i_bank_account.hpp" 6 | #include "event_loop.hpp" 7 | 8 | class ThreadSafeAccount : public IBankAccount 9 | { 10 | public: 11 | ThreadSafeAccount( 12 | std::shared_ptr eventLoop, 13 | std::shared_ptr unknownBankAccount) : 14 | m_eventLoop(std::move(eventLoop)), 15 | m_unknownBankAccount(std::move(unknownBankAccount)) 16 | { 17 | } 18 | 19 | void pay(unsigned amount) noexcept override 20 | { 21 | //don't use this alternative because [=] or [&] captures this, 22 | //but not std::shared_ptr. 23 | //m_eventLoop->enqueue([=]() 24 | //{ 25 | // m_unknownBankAccount->pay(amount); 26 | //}); 27 | 28 | //use this alternative instead 29 | m_eventLoop->enqueue(std::bind( 30 | &IBankAccount::pay, m_unknownBankAccount, amount)); 31 | } 32 | void acquire(unsigned amount) noexcept override 33 | { 34 | m_eventLoop->enqueue(std::bind( 35 | &IBankAccount::acquire, m_unknownBankAccount, amount)); 36 | } 37 | long long balance() const noexcept override 38 | { 39 | //capturing via [&] is perfectly valid here 40 | return m_eventLoop->enqueueSync([&] 41 | { 42 | return m_unknownBankAccount->balance(); 43 | }); 44 | 45 | //or you can use this variant for consistency 46 | //return m_eventLoop->enqueueSync( 47 | // &IBankAccount::balance, m_unknownBankAccount); 48 | } 49 | private: 50 | std::shared_ptr m_eventLoop; 51 | std::shared_ptr m_unknownBankAccount; 52 | }; -------------------------------------------------------------------------------- /EventLoop/AccessSerialization/thread_unsafe_account.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_bank_account.hpp" 4 | 5 | class ThreadUnsafeAccount : public IBankAccount 6 | { 7 | public: 8 | ThreadUnsafeAccount(long long balance) : m_balance(balance) 9 | { 10 | } 11 | void pay(unsigned amount) noexcept override 12 | { 13 | m_balance -= amount; 14 | } 15 | void acquire(unsigned amount) noexcept override 16 | { 17 | m_balance += amount; 18 | } 19 | long long balance() const noexcept override 20 | { 21 | return m_balance; 22 | } 23 | private: 24 | long long m_balance; 25 | }; -------------------------------------------------------------------------------- /EventLoop/Basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(EventLoop) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | event_loop.hpp 10 | main.cpp 11 | ) 12 | 13 | set_target_properties( 14 | ${PROJECT_NAME} 15 | PROPERTIES 16 | CXX_STANDARD 20 17 | CXX_STANDARD_REQUIRED YES 18 | CXX_EXTENSIONS NO 19 | ) 20 | 21 | target_compile_options( 22 | ${PROJECT_NAME} 23 | PRIVATE 24 | $<$:/W4 /WX> 25 | $<$>:-Wall -Wextra -Wpedantic> 26 | ) 27 | 28 | target_link_libraries( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | Threads::Threads 32 | ) 33 | -------------------------------------------------------------------------------- /EventLoop/Basic/event_loop.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class EventLoop { 10 | public: 11 | using callable_t = std::function; 12 | 13 | EventLoop() = default; 14 | EventLoop(const EventLoop&) = delete; 15 | EventLoop(EventLoop&&) noexcept = delete; 16 | ~EventLoop() noexcept { 17 | enqueue([this] { m_running = false; }); 18 | m_thread.join(); 19 | } 20 | 21 | EventLoop& operator=(const EventLoop&) = delete; 22 | EventLoop& operator=(EventLoop&&) noexcept = delete; 23 | 24 | void enqueue(callable_t&& callable) noexcept { 25 | { 26 | std::lock_guard guard(m_mutex); 27 | m_writeBuffer.emplace_back(std::move(callable)); 28 | } 29 | m_condVar.notify_one(); 30 | } 31 | 32 | template 33 | auto enqueueSync(Func&& callable, Args&&... args) { 34 | if (std::this_thread::get_id() == m_thread.get_id()) { 35 | return std::invoke(std::forward(callable), 36 | std::forward(args)...); 37 | } 38 | 39 | using return_type = std::invoke_result_t; 40 | using packaged_task_type = std::packaged_task; 41 | 42 | packaged_task_type task(std::forward(callable)); 43 | 44 | enqueue([&] { task(std::forward(args)...); }); 45 | 46 | return task.get_future().get(); 47 | } 48 | 49 | template 50 | [[nodiscard]] auto enqueueAsync(Func&& callable, Args&&... args) { 51 | using return_type = std::invoke_result_t; 52 | using packaged_task_type = std::packaged_task; 53 | 54 | auto taskPtr = std::make_shared( 55 | std::bind(std::forward(callable), std::forward(args)...)); 56 | 57 | enqueue(std::bind(&packaged_task_type::operator(), taskPtr)); 58 | 59 | return taskPtr->get_future(); 60 | } 61 | 62 | private: 63 | std::vector m_writeBuffer; 64 | std::mutex m_mutex; 65 | std::condition_variable m_condVar; 66 | bool m_running{true}; 67 | std::thread m_thread{&EventLoop::threadFunc, this}; 68 | 69 | void threadFunc() noexcept { 70 | std::vector readBuffer; 71 | 72 | while (m_running) { 73 | { 74 | std::unique_lock lock(m_mutex); 75 | m_condVar.wait(lock, [this] { return !m_writeBuffer.empty(); }); 76 | std::swap(readBuffer, m_writeBuffer); 77 | } 78 | 79 | for (callable_t& func : readBuffer) { 80 | func(); 81 | } 82 | 83 | readBuffer.clear(); 84 | } 85 | } 86 | }; -------------------------------------------------------------------------------- /EventLoop/Basic/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "event_loop.hpp" 4 | 5 | int main() { 6 | { 7 | EventLoop eventLoop; 8 | 9 | eventLoop.enqueue([] { std::cout << "message from a different thread\n"; }); 10 | 11 | std::future result = 12 | eventLoop.enqueueAsync([](int x, int y) { return x + y; }, 1, 2); 13 | 14 | std::cout << "enqueueSync " 15 | << eventLoop.enqueueSync( 16 | [](const int& x, int&& y, int z) { return x + y + z; }, 1, 17 | 2, 3) 18 | << '\n'; 19 | 20 | std::cout << "enqueueAsync " << result.get() << '\n'; 21 | 22 | std::cout << "prints before or after the message above\n"; 23 | } 24 | 25 | std::cout << "guaranteed to be printed the last\n"; 26 | } 27 | -------------------------------------------------------------------------------- /EventLoop/EventHandlers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(EventHandlers) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | event_loop.hpp 10 | main.cpp 11 | ) 12 | 13 | set_target_properties( 14 | ${PROJECT_NAME} 15 | PROPERTIES 16 | CXX_STANDARD 20 17 | CXX_STANDARD_REQUIRED YES 18 | CXX_EXTENSIONS NO 19 | ) 20 | 21 | target_compile_options( 22 | ${PROJECT_NAME} 23 | PRIVATE 24 | $<$:/W4 /WX> 25 | $<$>:-Wall -Wextra -Wpedantic> 26 | ) 27 | 28 | target_link_libraries( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | Threads::Threads 32 | ) 33 | -------------------------------------------------------------------------------- /EventLoop/EventHandlers/event_loop.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class EventLoop 10 | { 11 | public: 12 | using callable_t = std::function; 13 | 14 | EventLoop() = default; 15 | EventLoop(const EventLoop&) = delete; 16 | EventLoop(EventLoop&&) noexcept = delete; 17 | ~EventLoop() noexcept 18 | { 19 | enqueue([this] 20 | { 21 | m_running = false; 22 | }); 23 | m_thread.join(); 24 | } 25 | 26 | EventLoop& operator= (const EventLoop&) = delete; 27 | EventLoop& operator= (EventLoop&&) noexcept = delete; 28 | 29 | void enqueue(callable_t&& callable) noexcept 30 | { 31 | { 32 | std::lock_guard guard(m_mutex); 33 | m_writeBuffer.emplace_back(std::move(callable)); 34 | } 35 | m_condVar.notify_one(); 36 | } 37 | 38 | template 39 | auto enqueueSync(Func&& callable, Args&& ...args) 40 | { 41 | if (std::this_thread::get_id() == m_thread.get_id()) 42 | { 43 | return std::invoke( 44 | std::forward(callable), 45 | std::forward(args)...); 46 | } 47 | 48 | using return_type = std::invoke_result_t; 49 | using packaged_task_type = 50 | std::packaged_task; 51 | 52 | packaged_task_type task(std::forward(callable)); 53 | 54 | enqueue([&] 55 | { 56 | task(std::forward(args)...); 57 | }); 58 | 59 | return task.get_future().get(); 60 | } 61 | 62 | template 63 | [[nodiscard]] auto enqueueAsync(Func&& callable, Args&& ...args) 64 | { 65 | using return_type = std::invoke_result_t; 66 | using packaged_task_type = std::packaged_task; 67 | 68 | auto taskPtr = std::make_shared(std::bind( 69 | std::forward(callable), std::forward(args)...)); 70 | 71 | enqueue(std::bind(&packaged_task_type::operator(), taskPtr)); 72 | 73 | return taskPtr->get_future(); 74 | } 75 | 76 | private: 77 | std::vector m_writeBuffer; 78 | std::mutex m_mutex; 79 | std::condition_variable m_condVar; 80 | bool m_running{ true }; 81 | std::thread m_thread{ &EventLoop::threadFunc, this }; 82 | 83 | void threadFunc() noexcept 84 | { 85 | std::vector readBuffer; 86 | 87 | while (m_running) 88 | { 89 | { 90 | std::unique_lock lock(m_mutex); 91 | m_condVar.wait(lock, [this] 92 | { 93 | return !m_writeBuffer.empty(); 94 | }); 95 | std::swap(readBuffer, m_writeBuffer); 96 | } 97 | 98 | for (callable_t& func : readBuffer) 99 | { 100 | func(); 101 | } 102 | 103 | readBuffer.clear(); 104 | } 105 | } 106 | }; -------------------------------------------------------------------------------- /EventLoop/EventHandlers/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "event_loop.hpp" 4 | 5 | std::function)> OnNetworkEvent; 6 | 7 | void emitNetworkEvent(EventLoop& loop, std::vector data) 8 | { 9 | if (!OnNetworkEvent) return; 10 | 11 | loop.enqueue(std::bind(std::ref(OnNetworkEvent), std::move(data))); 12 | } 13 | 14 | int main() 15 | { 16 | //registering event handler 17 | OnNetworkEvent = [](std::vector message) 18 | { 19 | std::cout << message.size() << ' '; 20 | }; 21 | 22 | EventLoop loop; 23 | 24 | //let's trigger the event from different threads 25 | std::thread t1 = std::thread([](EventLoop& loop) 26 | { 27 | for (std::size_t i = 0; i < 10; ++i) 28 | { 29 | emitNetworkEvent(loop, std::vector(i)); 30 | } 31 | }, std::ref(loop)); 32 | 33 | std::thread t2 = std::thread([](EventLoop& loop) 34 | { 35 | for (int i = 10; i < 20; ++i) 36 | { 37 | emitNetworkEvent(loop, std::vector(i)); 38 | } 39 | }, std::ref(loop)); 40 | 41 | for (int i = 20; i < 30; ++i) 42 | { 43 | emitNetworkEvent(loop, std::vector(i)); 44 | } 45 | 46 | t1.join(); 47 | t2.join(); 48 | 49 | loop.enqueue([] 50 | { 51 | std::cout << std::endl; 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /Future/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(future) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | future.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /Future/README.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | * Dealing with Sharing Pattern 4 | 5 | * A future is a non-mutable placeholder for a value, which is set by a promise. 6 | 7 | -------------------------------------------------------------------------------- /Future/future.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | auto future = std::async(std::launch::async, [] { return "LazyOrEager"; }); 7 | std::cout << future.get() << '\n'; 8 | 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /GuardedSuspension/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(GuardedSuspension) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | bossWorker.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /GuardedSuspension/README.md: -------------------------------------------------------------------------------- 1 | # Guarded Suspension 2 | 3 | * Dealing with Mutation Pattern 4 | 5 | * A guarded suspension consists of a lock and a condition, which has to be fulfilled by the calling thread. 6 | 7 | ## Usage: 8 | 9 | * The calling thread will put itself to sleep if the condition is not 10 | meet. 11 | 12 | * The calling thread uses a lock when it checks the condition. 13 | 14 | * The lock protects the calling thread from a data race or deadlock. 15 | 16 | ## Variations: 17 | 18 | ### The waiting thread is notified about the state change or asks for the state change. 19 | 20 | * Push principle: condition variables, future/promise pairs, atomics (C++20), and semaphores (C++20) 21 | 22 | * Pull principle: not natively supported in C++ 23 | 24 | ### The waiting thread waits with or without a time limit. 25 | 26 | * Condition variables, and future/promise pairs 27 | 28 | ### The notification is sent to one or all waiting threads. 29 | 30 | * Shared futures, condition variables, atomics (C++20), and semaphores (C++20) -------------------------------------------------------------------------------- /GuardedSuspension/bossWorker.cpp: -------------------------------------------------------------------------------- 1 | // bossWorker.cpp 2 | // Guarded Suspension 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int getRandomTime(int start, int end){ 13 | 14 | std::random_device seed; 15 | std::mt19937 engine(seed()); 16 | std::uniform_int_distribution dist(start,end); 17 | 18 | return dist(engine); 19 | }; 20 | 21 | class Worker{ 22 | public: 23 | explicit Worker(const std::string& n):name(n){}; 24 | 25 | void operator() (std::promise&& preparedWork, 26 | std::shared_future boss2Worker){ 27 | 28 | // prepare the work and notfiy the boss 29 | int prepareTime= getRandomTime(500, 2000); 30 | std::this_thread::sleep_for(std::chrono::milliseconds(prepareTime)); 31 | preparedWork.set_value(); 32 | std::cout << name << ": " << "Work prepared after " 33 | << prepareTime << " milliseconds." << '\n'; 34 | 35 | // still waiting for the permission to start working 36 | boss2Worker.wait(); 37 | } 38 | private: 39 | std::string name; 40 | }; 41 | 42 | int main(){ 43 | 44 | std::cout << '\n'; 45 | 46 | // define the std::promise => Instruction from the boss 47 | std::promise startWorkPromise; 48 | 49 | // get the std::shared_future's from the std::promise 50 | std::shared_future startWorkFuture= startWorkPromise.get_future(); 51 | 52 | std::promise herbPrepared; 53 | std::future waitForHerb = herbPrepared.get_future(); 54 | Worker herb(" Herb"); 55 | std::thread herbWork(herb, std::move(herbPrepared), startWorkFuture); 56 | 57 | std::promise scottPrepared; 58 | std::future waitForScott = scottPrepared.get_future(); 59 | Worker scott(" Scott"); 60 | std::thread scottWork(scott, std::move(scottPrepared), startWorkFuture); 61 | 62 | std::promise bjarnePrepared; 63 | std::future waitForBjarne = bjarnePrepared.get_future(); 64 | Worker bjarne(" Bjarne"); 65 | std::thread bjarneWork(bjarne, std::move(bjarnePrepared), startWorkFuture); 66 | 67 | std::cout << "BOSS: PREPARE YOUR WORK.\n " << '\n'; 68 | 69 | // waiting for the worker 70 | waitForHerb.wait(), waitForScott.wait(), waitForBjarne.wait(); 71 | 72 | // notify the workers that they should begin to work 73 | std::cout << "\nBOSS: START YOUR WORK. \n" << '\n'; 74 | startWorkPromise.set_value(); 75 | 76 | herbWork.join(); 77 | scottWork.join(); 78 | bjarneWork.join(); 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vladislav Antonov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LockFree/AtomicSharedPointerStack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(atomic_smart_pointer_stack2) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | -------------------------------------------------------------------------------- /LockFree/AtomicSharedPointerStack/main.cpp: -------------------------------------------------------------------------------- 1 | // https://medium.com/@simontoth/daily-bit-e-of-c-std-atomic-std-shared-ptr-std-atomic-std-weak-ptr-f4a408611087 2 | #include 3 | #include 4 | #include 5 | 6 | // Example of a simple thread-safe 7 | // and resource-safe stack datastructure 8 | template 9 | struct Stack { 10 | struct Node { 11 | T value; 12 | std::shared_ptr prev; 13 | }; 14 | 15 | void push(T value) { 16 | // Make a new node, setting the current head as its previous value 17 | auto active = std::make_shared(std::move(value), head_.load()); 18 | 19 | // Another thread can come in a modify the current head, 20 | // so check if active->prev is still head_ 21 | // - if it's not, update active->prev to the current value of head_ and loop 22 | // - if it is, update head_ to active 23 | while (not head_.compare_exchange_weak(active->prev, active)); 24 | } 25 | 26 | std::optional pop() { 27 | // Load the current head 28 | auto active = head_.load(); 29 | 30 | // Another thread can come in and modify the current head, 31 | // so check if the head has changed (i.e. head_ != active) 32 | // - if it has changed, update active to the current head and loop 33 | // - if it hasn't changed, update the head to the previous element on the 34 | // stack 35 | while (active != nullptr && 36 | not head_.compare_exchange_weak(active, active->prev)); 37 | 38 | // If we didn't run out of elements, return the value 39 | if (active != nullptr) 40 | return {std::move(active->value)}; 41 | else 42 | return std::nullopt; 43 | } 44 | 45 | private: 46 | std::atomic> head_; 47 | }; 48 | 49 | #include 50 | #include 51 | #include 52 | 53 | int main() { 54 | Stack stack; 55 | std::array writers; 56 | std::array readers; 57 | 58 | // start the writers 59 | for (auto& t : writers) 60 | t = std::jthread{[&stack] { 61 | // write 100 values 62 | for (int i = 0; i < 100; i++) stack.push(42); 63 | }}; 64 | // start the readers 65 | for (auto& t : readers) 66 | t = std::jthread{[&stack] { 67 | // read 100 values 68 | for (int i = 0; i < 100; i++) 69 | while (stack.pop() == std::nullopt); 70 | }}; 71 | } 72 | -------------------------------------------------------------------------------- /LockFree/AtomicSmartPointerStack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(atomic_smart_pointer_stack) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | -------------------------------------------------------------------------------- /LockFree/AtomicSmartPointerStack/main.cpp: -------------------------------------------------------------------------------- 1 | // https://www.modernescpp.com/index.php/a-lock-free-stack-atomic-smart-pointer/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | class LockFreeStack { 10 | public: 11 | struct Node { 12 | T data; 13 | std::shared_ptr next; 14 | }; 15 | std::shared_ptr head; 16 | 17 | public: 18 | LockFreeStack() = default; 19 | LockFreeStack(const LockFreeStack&) = delete; 20 | LockFreeStack& operator=(const LockFreeStack&) = delete; 21 | 22 | void push(T val) { 23 | auto newNode = std::make_shared(); 24 | newNode->data = val; 25 | newNode->next = std::atomic_load(&head); // 1 26 | while (!std::atomic_compare_exchange_strong(&head, &newNode->next, 27 | newNode)); // 2 28 | } 29 | 30 | T topAndPop() { 31 | auto oldHead = std::atomic_load(&head); // 3 32 | while (oldHead && 33 | !std::atomic_compare_exchange_strong( 34 | &head, &oldHead, std::atomic_load(&oldHead->next))) { // 4 35 | if (!oldHead) throw std::out_of_range("The stack is empty!"); 36 | } 37 | return oldHead->data; 38 | } 39 | }; 40 | 41 | int main() { 42 | LockFreeStack lockFreeStack; 43 | 44 | auto fut = std::async([&lockFreeStack] { lockFreeStack.push(2011); }); 45 | auto fut1 = std::async([&lockFreeStack] { lockFreeStack.push(2014); }); 46 | auto fut2 = std::async([&lockFreeStack] { lockFreeStack.push(2017); }); 47 | 48 | auto fut3 = 49 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 50 | auto fut4 = 51 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 52 | auto fut5 = 53 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 54 | 55 | fut.get(), fut1.get(), fut2.get(); 56 | 57 | std::cout << fut3.get() << '\n'; 58 | std::cout << fut4.get() << '\n'; 59 | std::cout << fut5.get() << '\n'; 60 | } 61 | -------------------------------------------------------------------------------- /LockFree/CompleteStack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(complete_stack) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | -------------------------------------------------------------------------------- /LockFree/CompleteStack/main.cpp: -------------------------------------------------------------------------------- 1 | // https://www.modernescpp.com/index.php/a-lock-free-stack-a-complete-implementation/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | class LockFreeStack { 9 | private: 10 | struct Node { 11 | T data; 12 | Node* next; 13 | Node(T d) : data(d), next(nullptr) {} 14 | }; 15 | std::atomic head; 16 | 17 | public: 18 | LockFreeStack() = default; 19 | LockFreeStack(const LockFreeStack&) = delete; 20 | LockFreeStack& operator=(const LockFreeStack&) = delete; 21 | 22 | void push(T val) { 23 | Node* const newNode = new Node(val); 24 | newNode->next = head.load(); 25 | while (!head.compare_exchange_strong(newNode->next, newNode)); 26 | } 27 | 28 | T topAndPop() { 29 | Node* oldHead = head.load(); // 1 30 | while (oldHead && 31 | !head.compare_exchange_strong(oldHead, oldHead->next)) { // 2 32 | if (!oldHead) throw std::out_of_range("The stack is empty!"); // 3 33 | } 34 | return oldHead->data; // 4 35 | } 36 | }; 37 | 38 | int main() { 39 | LockFreeStack lockFreeStack; 40 | 41 | auto fut = std::async([&lockFreeStack] { lockFreeStack.push(2011); }); 42 | auto fut1 = std::async([&lockFreeStack] { lockFreeStack.push(2014); }); 43 | auto fut2 = std::async([&lockFreeStack] { lockFreeStack.push(2017); }); 44 | 45 | auto fut3 = 46 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 47 | auto fut4 = 48 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 49 | auto fut5 = 50 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 51 | 52 | fut.get(), fut1.get(), fut2.get(); // 5 53 | 54 | std::cout << fut3.get() << '\n'; 55 | std::cout << fut4.get() << '\n'; 56 | std::cout << fut5.get() << '\n'; 57 | } 58 | -------------------------------------------------------------------------------- /LockFree/HazardPointerStack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(hazard_pointer_stack) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | -------------------------------------------------------------------------------- /LockFree/HazardPointerStack/main.cpp: -------------------------------------------------------------------------------- 1 | // https://www.modernescpp.com/index.php/a-lock-free-stack-a-hazard-pointer-implementation/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | concept Node = requires(T a) { 12 | { T::data }; 13 | { *a.next } -> std::same_as; 14 | }; 15 | 16 | template 17 | struct MyNode { 18 | T data; 19 | MyNode* next; 20 | MyNode(T d) : data(d), next(nullptr) {} 21 | }; 22 | 23 | constexpr std::size_t MaxHazardPointers = 50; 24 | 25 | template > 26 | struct HazardPointer { 27 | std::atomic id; 28 | std::atomic pointer; 29 | }; 30 | 31 | template 32 | HazardPointer HazardPointers[MaxHazardPointers]; 33 | 34 | template > 35 | class HazardPointerOwner { 36 | HazardPointer* hazardPointer; 37 | 38 | public: 39 | HazardPointerOwner(HazardPointerOwner const&) = delete; 40 | HazardPointerOwner operator=(HazardPointerOwner const&) = delete; 41 | 42 | HazardPointerOwner() : hazardPointer(nullptr) { 43 | for (std::size_t i = 0; i < MaxHazardPointers; ++i) { 44 | std::thread::id old_id; 45 | if (HazardPointers[i].id.compare_exchange_strong( 46 | old_id, std::this_thread::get_id())) { 47 | hazardPointer = &HazardPointers[i]; 48 | break; 49 | } 50 | } 51 | if (!hazardPointer) { 52 | throw std::out_of_range("No hazard pointers available!"); 53 | } 54 | } 55 | 56 | std::atomic& getPointer() { return hazardPointer->pointer; } 57 | 58 | ~HazardPointerOwner() { 59 | hazardPointer->pointer.store(nullptr); 60 | hazardPointer->id.store(std::thread::id()); 61 | } 62 | }; 63 | 64 | template > 65 | std::atomic& getHazardPointer() { 66 | thread_local static HazardPointerOwner hazard; 67 | return hazard.getPointer(); 68 | } 69 | 70 | template > 71 | class RetireList { 72 | struct RetiredNode { 73 | MyNode* node; 74 | RetiredNode* next; 75 | RetiredNode(MyNode* p) : node(p), next(nullptr) {} 76 | ~RetiredNode() { delete node; } 77 | }; 78 | 79 | std::atomic RetiredNodes; 80 | 81 | void addToRetiredNodes(RetiredNode* retiredNode) { 82 | retiredNode->next = RetiredNodes.load(); 83 | while ( 84 | !RetiredNodes.compare_exchange_strong(retiredNode->next, retiredNode)); 85 | } 86 | 87 | public: 88 | bool isInUse(MyNode* node) { 89 | for (std::size_t i = 0; i < MaxHazardPointers; ++i) { 90 | if (HazardPointers[i].pointer.load() == node) return true; 91 | } 92 | return false; 93 | } 94 | 95 | void addNode(MyNode* node) { addToRetiredNodes(new RetiredNode(node)); } 96 | 97 | void deleteUnusedNodes() { 98 | RetiredNode* current = RetiredNodes.exchange(nullptr); 99 | while (current) { 100 | RetiredNode* const next = current->next; 101 | if (!isInUse(current->node)) 102 | delete current; 103 | else 104 | addToRetiredNodes(current); 105 | current = next; 106 | } 107 | } 108 | }; 109 | 110 | template > 111 | class LockFreeStack { 112 | std::atomic head; 113 | RetireList retireList; 114 | 115 | public: 116 | LockFreeStack() = default; 117 | LockFreeStack(const LockFreeStack&) = delete; 118 | LockFreeStack& operator=(const LockFreeStack&) = delete; 119 | 120 | void push(T val) { 121 | MyNode* const newMyNode = new MyNode(val); 122 | newMyNode->next = head.load(); 123 | while (!head.compare_exchange_strong(newMyNode->next, newMyNode)); 124 | } 125 | 126 | T topAndPop() { 127 | std::atomic& hazardPointer = getHazardPointer(); 128 | MyNode* oldHead = head.load(); 129 | do { 130 | MyNode* tempMyNode; 131 | do { 132 | tempMyNode = oldHead; 133 | hazardPointer.store(oldHead); 134 | oldHead = head.load(); 135 | } while (oldHead != tempMyNode); 136 | } while (oldHead && !head.compare_exchange_strong(oldHead, oldHead->next)); 137 | if (!oldHead) throw std::out_of_range("The stack is empty!"); 138 | hazardPointer.store(nullptr); 139 | auto res = oldHead->data; 140 | if (retireList.isInUse(oldHead)) 141 | retireList.addNode(oldHead); 142 | else 143 | delete oldHead; 144 | retireList.deleteUnusedNodes(); 145 | return res; 146 | } 147 | }; 148 | 149 | int main() { 150 | LockFreeStack lockFreeStack; 151 | 152 | auto fut = std::async([&lockFreeStack] { lockFreeStack.push(2011); }); 153 | auto fut1 = std::async([&lockFreeStack] { lockFreeStack.push(2014); }); 154 | auto fut2 = std::async([&lockFreeStack] { lockFreeStack.push(2017); }); 155 | 156 | auto fut3 = 157 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 158 | auto fut4 = 159 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 160 | auto fut5 = 161 | std::async([&lockFreeStack] { return lockFreeStack.topAndPop(); }); 162 | 163 | fut.get(), fut1.get(), fut2.get(); 164 | 165 | std::cout << fut3.get() << '\n'; 166 | std::cout << fut4.get() << '\n'; 167 | std::cout << fut5.get() << '\n'; 168 | } 169 | -------------------------------------------------------------------------------- /LockFree/README.md: -------------------------------------------------------------------------------- 1 | # Lock Free Data Structures 2 | 3 | ## Lock free queue types 4 | * Consumers - single, multi 5 | * Producers - single, multi 6 | * Pop on empty - return false, return sentinel 7 | * Push when full - return false, overwrite 8 | * Favour - readers, writers 9 | 10 | ## Examples 11 | * [SPSC Lock-free, Wait-free Fifo from the Ground Up](https://github.com/CharlesFrasch/cppcon2023) 12 | * 13 | * 14 | -------------------------------------------------------------------------------- /LockFree/SimpleStack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(simple_stack) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | -------------------------------------------------------------------------------- /LockFree/SimpleStack/main.cpp: -------------------------------------------------------------------------------- 1 | // https://www.modernescpp.com/index.php/a-lock-free-stack-a-simplified-implementation/ 2 | #include 3 | #include 4 | 5 | template 6 | class LockFreeStackPush { 7 | private: 8 | struct Node { 9 | T data; 10 | Node* next; 11 | Node(T d) : data(d), next(nullptr) {} 12 | }; 13 | std::atomic head; 14 | 15 | public: 16 | LockFreeStackPush() = default; 17 | LockFreeStackPush(const LockFreeStackPush&) = delete; 18 | LockFreeStackPush& operator=(const LockFreeStackPush&) = delete; 19 | 20 | void push(T val) { 21 | Node* const newNode = new Node(val); // 1 22 | newNode->next = head.load(); // 2 23 | while (!head.compare_exchange_strong(newNode->next, newNode)); // 3 24 | } 25 | }; 26 | 27 | int main() { 28 | LockFreeStackPush lockFreeStack; 29 | lockFreeStack.push(5); 30 | 31 | LockFreeStackPush lockFreeStack2; 32 | lockFreeStack2.push(5.5); 33 | 34 | LockFreeStackPush lockFreeStack3; 35 | lockFreeStack3.push("hello"); 36 | } 37 | -------------------------------------------------------------------------------- /MonitorObject/Cpp11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(MonitorObjectCpp11) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | monitorObject.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 11 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /MonitorObject/Cpp11/monitorObject.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class Monitor { 11 | public: 12 | void lock() const { 13 | monitMutex.lock(); 14 | } 15 | void unlock() const { 16 | monitMutex.unlock(); 17 | } 18 | 19 | void notify_one() const noexcept { 20 | monitCond.notify_one(); 21 | } 22 | 23 | template 24 | void wait(Predicate pred) const { 25 | std::unique_lock monitLock(monitMutex); 26 | monitCond.wait(monitLock, pred); 27 | } 28 | 29 | private: 30 | mutable std::mutex monitMutex; 31 | mutable std::condition_variable monitCond; 32 | }; 33 | 34 | template 35 | class ThreadSafeQueue: public Monitor>{ 36 | public: 37 | void add(T val){ 38 | monitorForQueue.lock(); 39 | myQueue.push(val); 40 | monitorForQueue.unlock(); 41 | monitorForQueue.notify_one(); 42 | } 43 | 44 | T get(){ 45 | monitorForQueue.wait( [this] { return ! myQueue.empty(); } ); 46 | monitorForQueue.lock(); 47 | auto val = myQueue.front(); 48 | myQueue.pop(); 49 | monitorForQueue.unlock(); 50 | return val; 51 | } 52 | 53 | private: 54 | std::queue myQueue; 55 | ThreadSafeQueue& monitorForQueue = static_cast&>(*this); 56 | }; 57 | 58 | 59 | class Dice { 60 | public: 61 | int operator()(){ return rand(); } 62 | private: 63 | std::function rand = std::bind(std::uniform_int_distribution<>(1, 6), 64 | std::default_random_engine()); 65 | }; 66 | 67 | 68 | int main(){ 69 | 70 | std::cout << '\n'; 71 | 72 | constexpr auto NumberThreads = 10; 73 | 74 | ThreadSafeQueue safeQueue; 75 | 76 | auto addLambda = [&safeQueue](int val){ safeQueue.add(val); 77 | std::cout << val << " " 78 | << std::this_thread::get_id() << "; "; 79 | }; 80 | auto getLambda = [&safeQueue]{ safeQueue.get(); }; 81 | 82 | std::vector addThreads(NumberThreads); 83 | Dice dice; 84 | for (auto& thr: addThreads) thr = std::thread(addLambda, dice() ); 85 | 86 | std::vector getThreads(NumberThreads); 87 | for (auto& thr: getThreads) thr = std::thread(getLambda); 88 | 89 | for (auto& thr: addThreads) thr.join(); 90 | for (auto& thr: getThreads) thr.join(); 91 | 92 | std::cout << "\n\n"; 93 | 94 | } 95 | -------------------------------------------------------------------------------- /MonitorObject/Cpp20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(MonitorObjectCpp11) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | monitorObjectCpp20.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /MonitorObject/Cpp20/monitorObjectCpp20.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | class Monitor { 12 | public: 13 | void lock() const { 14 | monitMutex.lock(); 15 | } 16 | void unlock() const { 17 | monitMutex.unlock(); 18 | } 19 | 20 | void notify_one() const noexcept { 21 | monitCond.notify_one(); 22 | } 23 | 24 | template 25 | void wait(Predicate pred) const { 26 | std::unique_lock monitLock(monitMutex); 27 | monitCond.wait(monitLock, pred); 28 | } 29 | 30 | private: 31 | mutable std::mutex monitMutex; 32 | mutable std::condition_variable monitCond; 33 | }; 34 | 35 | template 36 | class ThreadSafeQueue: public Monitor>{ 37 | public: 38 | void add(T val){ 39 | monitorForQueue.lock(); 40 | myQueue.push(val); 41 | monitorForQueue.unlock(); 42 | monitorForQueue.notify_one(); 43 | } 44 | 45 | T get(){ 46 | monitorForQueue.wait( [this] { return ! myQueue.empty(); } ); 47 | monitorForQueue.lock(); 48 | auto val = myQueue.front(); 49 | myQueue.pop(); 50 | monitorForQueue.unlock(); 51 | return val; 52 | } 53 | 54 | private: 55 | std::queue myQueue; 56 | ThreadSafeQueue& monitorForQueue = static_cast&>(*this); 57 | }; 58 | 59 | 60 | class Dice { 61 | public: 62 | int operator()(){ return rand(); } 63 | private: 64 | std::function rand = std::bind(std::uniform_int_distribution<>(1, 6), 65 | std::default_random_engine()); 66 | }; 67 | 68 | 69 | int main(){ 70 | 71 | std::cout << '\n'; 72 | 73 | constexpr auto NumberThreads = 10; 74 | 75 | ThreadSafeQueue safeQueue; 76 | 77 | auto addLambda = [&safeQueue](int val){ safeQueue.add(val); 78 | std::cout << val << " " 79 | << std::this_thread::get_id() << "; "; 80 | }; 81 | auto getLambda = [&safeQueue]{ safeQueue.get(); }; 82 | 83 | std::vector getThreads(NumberThreads); 84 | for (auto& thr: getThreads) thr = std::jthread(getLambda); 85 | 86 | std::vector addThreads(NumberThreads); 87 | Dice dice; 88 | for (auto& thr: addThreads) thr = std::jthread(addLambda, dice() ); 89 | 90 | std::cout << "\n\n"; 91 | 92 | } 93 | -------------------------------------------------------------------------------- /MonitorObject/README.md: -------------------------------------------------------------------------------- 1 | # Monitor Object 2 | 3 | * Concurrent Architecture Pattern 4 | 5 | * The monitor object synchronizes the access to an object so that at most one member function can run at any moment in time. 6 | 7 | ## Summary 8 | 9 | * Each object has a monitor lock and a monitor condition. 10 | 11 | * The monitor lock guarantees that only one client can execute a member function of the object. 12 | 13 | * The monitor condition notifies the waiting clients. 14 | 15 | ## Components 16 | 17 | ### Monitor Object 18 | 19 | * Support member functions, which can run in the thread of the client. 20 | 21 | ### Synchronized Methods 22 | 23 | * Interface member functions of the monitor object. 24 | * At most, one member function can run at any point in time 25 | * The member functions should apply the thread-safe interface pattern. 26 | 27 | ### Monitor Lock 28 | 29 | * Each monitor object has a monitor lock. 30 | 31 | * Guarantees exclusive access to the member functions. 32 | 33 | ### Monitor Condition 34 | 35 | * Allows various threads to store their member function invocation. 36 | 37 | * When the current thread is done with its member function execution, the next thread is awoken. 38 | 39 | ## Advantages 40 | 41 | * The synchronization is encapsulated in the implementation. 42 | 43 | * The member function execution is automatically stored and performed. 44 | 45 | * The monitor object is a simple scheduler. 46 | 47 | ## Disadvantages 48 | 49 | * The synchronization mechanism and the functionality are strongly coupled and can, therefore, not easily be changed. 50 | 51 | * When the synchronized member functions invoke an additional member function of the monitor object, a deadlock may happen. 52 | -------------------------------------------------------------------------------- /ProducerConsumer/README.md: -------------------------------------------------------------------------------- 1 | ## Producer/Consumer pattern 2 | The Producer/Consumer pattern is used to decouple processes that produce and consume data at different rates. The Producer/Consumer pattern's parallel loops are broken down into two categories; those that produce data, and those that consume the data produced. 3 | 4 | ### Dijkstra's bounded buffer 5 | * The buffer can store N portions or elements. 6 | * The "number of queueing portions" semaphore counts the filled locations in the buffer 7 | * The "number of empty positions" semaphore counts the empty locations in the buffer 8 | * The "buffer manipulation" semaphore works as mutex for the buffer put and get operations. 9 | 10 | ### Using monitors 11 | Monitor is a shared variable and the set of meaningful operations on it. It is used to control the scheduling of resources among individual processes. 12 | 13 | ### Using channels 14 | An alternative to explicit naming of source and destination would be to name a port through which communication is to take place. 15 | 16 | ### Lamport's without semaphores 17 | Bounded buffer solution for one producer and one consumer. 18 | 19 | ## Reader/Writer pattern 20 | The Reader-Writer pattern addresses scenarios where multiple threads read shared data, but only one thread can write at a time. 21 | 22 | ### First readers-writers problem 23 | No reader shall be kept waiting if the share is currently opened for reading (readers-preference). 24 | 25 | ### Second readers-writers problem 26 | No writer, once added to the queue, shall be kept waiting longer than absolutely necessary (writers-preference). 27 | 28 | ### Third readers-writers problem 29 | No thread shall be allowed to starve 30 | 31 | ### Simplest reader writer problem 32 | Uses only two semaphores and doesn't need an array of readers to read the data in buffer. 33 | 34 | ## References 35 | * [Producer–consumer problem](https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem) 36 | * [Readers–writers problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem) 37 | * [Implementing Go-style Channels in C++ from scratch](https://jyotinder.substack.com/p/implementing-go-channels-in-cpp) 38 | * 39 | * [C++ Channel: A thread-safe container for sharing data between threads](https://blog.andreiavram.ro/cpp-channel-thread-safe-container-share-data-threads/) 40 | * 41 | * [User API & C++ Implementation of a Multi Producer, Multi Consumer, Lock Free, Atomic Queue - CppCon](https://www.youtube.com/watch?v=bjz_bMNNWRk), [code](https://github.com/erez-strauss/lockfree_mpmc_queue) 42 | 43 | -------------------------------------------------------------------------------- /ProducerConsumer/first_readers_writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(first_readers_writers) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/first_readers_writers/main.cpp: -------------------------------------------------------------------------------- 1 | // readers-preference: no reader shall be kept waiting if the share is currently 2 | // opened for reading 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | constexpr int kMaxMessagesCount = 10; 12 | 13 | std::atomic_uint messagesCount{0}; 14 | 15 | std::binary_semaphore resource{1}; 16 | std::binary_semaphore rmutex{1}; 17 | 18 | std::mutex countGuard; 19 | uint32_t readcount{0}; 20 | 21 | std::mutex dataGuard; 22 | std::queue data; 23 | 24 | void writer(int value) { 25 | // Lock the shared file for a writer 26 | resource.acquire(); 27 | { 28 | // Writing is done 29 | std::lock_guard lock{dataGuard}; 30 | std::cout << std::this_thread::get_id() << "\tWrite: " << value << '\n'; 31 | data.push(value); 32 | } 33 | // Release the shared file for use by other readers. Writers are allowed if 34 | // there are no readers requesting it. 35 | resource.release(); 36 | } 37 | 38 | void startRead() { 39 | // Ensure that no other reader can execute the section while you are 40 | // in it 41 | rmutex.acquire(); 42 | { 43 | std::lock_guard lock{countGuard}; 44 | // Indicate that you are a reader trying to enter the Critical Section 45 | readcount++; 46 | 47 | // Checks if you are the first reader trying to enter CS 48 | if (readcount == 1) { 49 | // If you are the first reader, lock the resource from writers. Resource 50 | // stays reserved for subsequent readers 51 | resource.acquire(); 52 | } 53 | } 54 | rmutex.release(); 55 | } 56 | 57 | void stopRead() { 58 | // Ensure that no other reader can execute the section while you are in 59 | // it 60 | rmutex.acquire(); 61 | { 62 | std::lock_guard lock{countGuard}; 63 | // Indicate that you no longer need the shared resource. One fewer reader 64 | readcount--; 65 | // Checks if you are the last (only) reader who is reading the shared file 66 | if (readcount == 0) { 67 | // If you are last reader, then you can unlock the resource. This makes it 68 | // available to writers. 69 | resource.release(); 70 | } 71 | } 72 | rmutex.release(); 73 | } 74 | 75 | std::optional reader() { 76 | std::optional result; 77 | 78 | startRead(); 79 | 80 | { 81 | // Do the Reading 82 | std::lock_guard lock{dataGuard}; 83 | if (!data.empty()) { 84 | result = data.front(); 85 | data.pop(); 86 | std::cout << std::this_thread::get_id() << "\tRead: " << *result << '\n'; 87 | } else { 88 | std::cout << std::this_thread::get_id() << "\tRead done!\n"; 89 | } 90 | } 91 | 92 | stopRead(); 93 | 94 | return result; 95 | } 96 | 97 | int main() { 98 | std::jthread t1{[]() { 99 | for (int i = 0; i < kMaxMessagesCount; i++) { 100 | writer(messagesCount.fetch_add(1)); 101 | } 102 | }}; 103 | 104 | std::jthread t2{[]() { 105 | while (auto result = reader()) { 106 | // Process result here 107 | }; 108 | }}; 109 | 110 | std::jthread t3{[]() { 111 | for (int i = 0; i < kMaxMessagesCount; i++) { 112 | writer(messagesCount.fetch_add(1)); 113 | } 114 | }}; 115 | 116 | std::jthread t4{[]() { 117 | while (auto result = reader()) { 118 | // Process result here 119 | }; 120 | }}; 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_bounded_buffer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_bounded_buffer) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_bounded_buffer/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | constexpr int kMaxMessagesCount = 10; 9 | 10 | constexpr int kBufferSize = 3; 11 | 12 | constexpr auto kTimeout = std::chrono::seconds(1); 13 | 14 | std::atomic_uint messagesCount{0}; 15 | 16 | std::counting_semaphore numberQueueingPortions{0}; 17 | std::counting_semaphore numberEmptyPositions{kBufferSize}; 18 | 19 | std::mutex bufferManipulation; 20 | 21 | struct Portion { 22 | unsigned int data{}; 23 | }; 24 | 25 | std::queue buffer; 26 | 27 | auto produce_next_portion() { 28 | return Portion{.data = messagesCount.fetch_add(1)}; 29 | } 30 | 31 | void add_portion_to_buffer(Portion&& portion) { buffer.emplace(portion); } 32 | 33 | std::optional take_portion_from_buffer() { 34 | if (buffer.empty()) { 35 | return std::nullopt; 36 | } 37 | auto portion = buffer.front(); 38 | buffer.pop(); 39 | return portion; 40 | } 41 | 42 | void process_portion_taken([[maybe_unused]] Portion&& portion) {} 43 | 44 | void producer() { 45 | for (int i = 0; i < kMaxMessagesCount; i++) { 46 | Portion portion = produce_next_portion(); 47 | 48 | numberEmptyPositions.acquire(); 49 | { 50 | std::lock_guard lock{bufferManipulation}; 51 | std::cout << std::this_thread::get_id() << "\tProduce: " << portion.data 52 | << '\n'; 53 | add_portion_to_buffer(std::move(portion)); 54 | } 55 | numberQueueingPortions.release(); 56 | } 57 | } 58 | 59 | void consumer() { 60 | for (;;) { 61 | numberQueueingPortions.try_acquire_for(kTimeout); 62 | Portion portion; 63 | { 64 | std::lock_guard lock{bufferManipulation}; 65 | 66 | auto result = take_portion_from_buffer(); 67 | if (!result) { 68 | return; 69 | } 70 | 71 | portion = *result; 72 | std::cout << std::this_thread::get_id() << "\tConsume: " << portion.data 73 | << '\n'; 74 | } 75 | numberEmptyPositions.release(); 76 | process_portion_taken(std::move(portion)); 77 | } 78 | } 79 | 80 | int main() { 81 | std::jthread t1{producer}; 82 | std::jthread t2{consumer}; 83 | std::jthread t3{producer}; 84 | std::jthread t4{consumer}; 85 | 86 | return 0; 87 | } -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_channel) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | channel.hpp 10 | selector.cpp 11 | main.cpp 12 | ) 13 | 14 | set_target_properties( 15 | ${PROJECT_NAME} 16 | PROPERTIES 17 | CXX_STANDARD 20 18 | CXX_STANDARD_REQUIRED YES 19 | CXX_EXTENSIONS NO 20 | ) 21 | 22 | target_compile_options( 23 | ${PROJECT_NAME} 24 | PRIVATE 25 | $<$:/W4 /WX> 26 | $<$>:-Wall -Wextra -Wpedantic> 27 | ) 28 | 29 | if (USE_TSAN) 30 | target_compile_options( 31 | ${PROJECT_NAME} 32 | PRIVATE 33 | $<$:-fsanitize=thread> 34 | ) 35 | 36 | target_link_libraries( 37 | ${PROJECT_NAME} 38 | PRIVATE 39 | $<$:-fsanitize=thread> 40 | ) 41 | endif() 42 | 43 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/channel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "selector.hpp" 14 | 15 | template 16 | class Channel { 17 | public: 18 | /** 19 | * @brief Constructs a Channel object. 20 | * @param cap The capacity of the channel. If 0, creates an unbuffered 21 | * channel. 22 | * 23 | * Use Case: Create buffered or unbuffered channels for inter-thread 24 | * communication. Example: Channel ch(5); // Creates a buffered channel 25 | * with capacity 5 Channel ch; // Creates an unbuffered channel 26 | */ 27 | Channel(size_t cap = 0) : capacity(cap) {} 28 | 29 | // Disable copying and moving 30 | Channel(const Channel&) = delete; 31 | Channel& operator=(const Channel&) = delete; 32 | Channel(Channel&&) = delete; 33 | Channel& operator=(Channel&&) = delete; 34 | 35 | // Destructor 36 | ~Channel() { 37 | close(); // Ensure the channel is closed before destruction 38 | } 39 | 40 | /** 41 | * @brief Sends a value to the channel. Blocks if the channel is full 42 | * (buffered) or if there's no receiver (unbuffered). 43 | * @param value The value to send. 44 | * @throws std::runtime_error if the channel is closed. 45 | * 46 | * Use Case: Send data to other threads in a blocking manner. 47 | * Example: ch.send(42); 48 | */ 49 | void send(const T& value); 50 | 51 | /** 52 | * @brief Asynchronously sends a value to the channel. 53 | * @param value The value to send. 54 | * @return A std::future representing the asynchronous operation. 55 | * 56 | * Use Case: Send data without blocking the current thread. 57 | * Example: auto future = ch.async_send(42); 58 | * future.wait(); // Wait for the send to complete if needed 59 | */ 60 | std::future async_send(const T& value); 61 | 62 | /** 63 | * @brief Attempts to send a value to the channel without blocking. 64 | * @param value The value to send. 65 | * @return true if the value was sent, false otherwise. 66 | * 67 | * Use Case: Try to send data without blocking, useful for timeouts or 68 | * polling. Example: if (ch.try_send(42)) { std::cout << "Sent 69 | * successfully\n"; 70 | * } 71 | */ 72 | bool try_send(const T& value); 73 | 74 | /** 75 | * @brief Receives a value from the channel. Blocks if the channel is empty. 76 | * @return An optional containing the received value, or std::nullopt if the 77 | * channel is closed and empty. 78 | * 79 | * Use Case: Receive data from other threads in a blocking manner. 80 | * Example: auto value = ch.receive(); 81 | * if (value) { 82 | * std::cout << "Received: " << *value << "\n"; 83 | * } 84 | */ 85 | std::optional receive(); 86 | 87 | /** 88 | * @brief Asynchronously receives a value from the channel. 89 | * @return A std::future containing an optional with the received value. 90 | * 91 | * Use Case: Receive data without blocking the current thread. 92 | * Example: auto future = ch.async_receive(); 93 | * auto value = future.get(); 94 | */ 95 | std::future> async_receive(); 96 | /** 97 | * @brief Attempts to receive a value from the channel without blocking. 98 | * @return An optional containing the received value, or std::nullopt if the 99 | * channel is empty. 100 | * 101 | * Use Case: Try to receive data without blocking, useful for timeouts or 102 | * polling. Example: 103 | * if (auto value = ch.try_receive()) { 104 | * std::cout << "Received: " << *value << "\n"; 105 | * } 106 | */ 107 | std::optional try_receive(); 108 | 109 | /** 110 | * @brief Closes the channel. No more values can be sent after closing. 111 | * 112 | * Use Case: Signal that no more values will be sent on this channel. 113 | * Example: ch.close(); 114 | */ 115 | void close(); 116 | 117 | /** 118 | * @brief Checks if the channel is closed. 119 | * @return true if the channel is closed, false otherwise. 120 | * 121 | * Use Case: Check the channel state before sending or in a loop. 122 | * Example: 123 | * while (!ch.is_closed()) { 124 | * // Perform operations 125 | * } 126 | */ 127 | bool is_closed() const { 128 | std::unique_lock lock(mtx); 129 | return closed; 130 | } 131 | 132 | /** 133 | * @brief Checks if the channel is empty. 134 | * @return true if the channel is empty, false otherwise. 135 | * 136 | * Use Case: Check if there are any pending values in the channel. 137 | * Example: 138 | * if (!ch.is_empty()) { 139 | * auto value = ch.receive(); 140 | * } 141 | */ 142 | bool is_empty() const { 143 | std::unique_lock lock(mtx); 144 | return queue.empty(); 145 | } 146 | 147 | /** 148 | * @brief Returns the current number of items in the channel. 149 | * @return The number of items currently in the channel. 150 | * 151 | * Use Case: Check how many items are waiting in the channel. 152 | * Example: std::cout << "Items in channel: " << ch.size() << "\n"; 153 | */ 154 | size_t size() const { 155 | std::unique_lock lock(mtx); 156 | return queue.size(); 157 | } 158 | 159 | private: 160 | /** 161 | * @brief Registers a selector with the channel. 162 | * 163 | * @param selector The selector to register. 164 | * @note This function is called by the Selector class and should not be 165 | * called directly. 166 | * @see Selector 167 | */ 168 | void register_selector(Selector* selector); 169 | 170 | /** 171 | * @brief Unregisters a selector from the channel. 172 | * 173 | * @param selector The selector to unregister. 174 | * @note This function is called by the Selector class and should not be 175 | * called directly. 176 | * @see Selector 177 | */ 178 | void unregister_selector(Selector* selector); 179 | 180 | std::queue queue; 181 | mutable std::mutex mtx; 182 | std::condition_variable cv_send, cv_recv; 183 | bool closed = false; 184 | size_t capacity; 185 | size_t waitingReceivers = 0; 186 | 187 | friend class Selector; 188 | std::vector selectors; 189 | }; 190 | 191 | #include "channel.tpp" 192 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/channel.tpp: -------------------------------------------------------------------------------- 1 | #include "channel.hpp" 2 | 3 | template 4 | void Channel::send(const T& value) { 5 | std::unique_lock lock(mtx); 6 | if (closed) { 7 | throw std::runtime_error("Send on closed channel"); 8 | } 9 | if (capacity == 0) { 10 | // For unbuffered channels, wait until there's a receiver or the channel 11 | // is closed 12 | cv_send.wait(lock, [this] { return waitingReceivers > 0 || closed; }); 13 | if (closed) { 14 | throw std::runtime_error("Channel closed while waiting to send"); 15 | } 16 | --waitingReceivers; 17 | queue.push(value); 18 | cv_recv.notify_one(); // Notify a waiting receiver 19 | // Notify all registered selectors 20 | for (auto selector : selectors) { 21 | selector->notify(); 22 | } 23 | } else { 24 | // For buffered channels, wait until there's space in the buffer or the 25 | // channel is closed 26 | cv_send.wait(lock, [this] { return queue.size() < capacity || closed; }); 27 | if (closed) { 28 | throw std::runtime_error("Channel closed while waiting to send"); 29 | } 30 | queue.push(value); 31 | cv_recv.notify_one(); // Notify a waiting receiver 32 | // Notify all registered selectors 33 | for (auto selector : selectors) { 34 | selector->notify(); 35 | } 36 | } 37 | } 38 | 39 | template 40 | std::future Channel::async_send(const T& value) { 41 | // Launch an asynchronous task to send the value 42 | return std::async(std::launch::async, [this, value] { this->send(value); }); 43 | } 44 | 45 | template 46 | bool Channel::try_send(const T& value) { 47 | std::unique_lock lock(mtx); 48 | // If the channel is closed or the buffer is full, return false 49 | if (closed || (capacity != 0 && queue.size() >= capacity)) { 50 | return false; 51 | } 52 | queue.push(value); 53 | cv_recv.notify_one(); // Notify a waiting receiver 54 | // Notify all registered selectors 55 | for (auto selector : selectors) { 56 | selector->notify(); 57 | } 58 | return true; 59 | } 60 | 61 | template 62 | std::optional Channel::receive() { 63 | std::unique_lock lock(mtx); 64 | if (capacity == 0) { 65 | // For unbuffered channels, notify a sender and wait for a value 66 | ++waitingReceivers; 67 | cv_send.notify_one(); 68 | cv_recv.wait(lock, [this] { return !queue.empty() || closed; }); 69 | --waitingReceivers; 70 | } else { 71 | // For buffered channels, wait until there's a value or the channel is 72 | // closed 73 | cv_recv.wait(lock, [this] { return !queue.empty() || closed; }); 74 | } 75 | if (queue.empty() && closed) { 76 | return std::nullopt; // Return empty optional if channel is closed and 77 | // empty 78 | } 79 | T value = queue.front(); 80 | queue.pop(); 81 | cv_send.notify_one(); // Notify a waiting sender 82 | return value; 83 | } 84 | 85 | template 86 | std::future> Channel::async_receive() { 87 | // Launch an asynchronous task to receive a value 88 | return std::async(std::launch::async, [this] { return this->receive(); }); 89 | } 90 | 91 | template 92 | std::optional Channel::try_receive() { 93 | std::unique_lock lock(mtx); 94 | if (queue.empty()) { 95 | return std::nullopt; // Return empty optional if queue is empty 96 | } 97 | T value = queue.front(); 98 | queue.pop(); 99 | cv_send.notify_one(); // Notify a waiting sender 100 | return value; 101 | } 102 | 103 | template 104 | void Channel::close() { 105 | std::unique_lock lock(mtx); 106 | closed = true; 107 | cv_send.notify_all(); // Notify all waiting senders 108 | cv_recv.notify_all(); // Notify all waiting receivers 109 | // Notify all registered selectors 110 | for (auto selector : selectors) { 111 | selector->notify(); 112 | } 113 | } 114 | 115 | template 116 | void Channel::register_selector(Selector* selector) { 117 | std::unique_lock lock(mtx); 118 | selectors.push_back(selector); 119 | } 120 | 121 | template 122 | void Channel::unregister_selector(Selector* selector) { 123 | std::unique_lock lock(mtx); 124 | // Remove the selector from the list 125 | selectors.erase(std::remove(selectors.begin(), selectors.end(), selector), 126 | selectors.end()); 127 | } -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/main.cpp: -------------------------------------------------------------------------------- 1 | // https://jyotinder.substack.com/p/implementing-go-channels-in-cpp 2 | // https://github.com/JyotinderSingh/CppChan/ 3 | 4 | #include 5 | #include 6 | 7 | #include "channel.hpp" 8 | 9 | constexpr int kMaxMessagesCount = 10; 10 | 11 | constexpr size_t kBufferSize = 3; 12 | 13 | std::atomic_uint messagesCount{0}; 14 | 15 | // printing only 16 | // thread sanitizer does not detect data race 17 | // valgrind detects data race 18 | std::mutex printMutex; 19 | 20 | struct Portion { 21 | unsigned int data{}; 22 | }; 23 | 24 | auto produce_next_portion() { 25 | return Portion{.data = messagesCount.fetch_add(1)}; 26 | } 27 | 28 | void process_portion_taken([[maybe_unused]] Portion&& portion) {} 29 | 30 | Channel buffer{kBufferSize}; 31 | 32 | void producer() { 33 | for (int i = 0; i < kMaxMessagesCount; i++) { 34 | Portion portion = produce_next_portion(); 35 | 36 | // Printing only 37 | { 38 | std::lock_guard lock{printMutex}; 39 | std::cout << std::this_thread::get_id() << "\tProduced: " << portion.data 40 | << '\n'; 41 | } 42 | 43 | if (!buffer.try_send(portion) && buffer.is_closed()) { 44 | return; 45 | } 46 | } 47 | } 48 | 49 | void consumer() { 50 | for (;;) { 51 | if (auto portion = buffer.receive()) { 52 | // Printing only 53 | { 54 | std::lock_guard lock{printMutex}; 55 | std::cout << std::this_thread::get_id() 56 | << "\tConsumed: " << portion->data << '\n'; 57 | } 58 | 59 | process_portion_taken(std::move(*portion)); 60 | } else { 61 | return; 62 | } 63 | } 64 | } 65 | 66 | int main() { 67 | std::jthread t1{producer}; 68 | std::jthread t2{consumer}; 69 | std::jthread t3{producer}; 70 | std::jthread t4{consumer}; 71 | 72 | std::this_thread::sleep_for(std::chrono::seconds(2)); 73 | // Printing only 74 | { 75 | std::lock_guard lock{printMutex}; 76 | std::cout << "Close channel\n"; 77 | buffer.close(); 78 | } 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/selector.cpp: -------------------------------------------------------------------------------- 1 | #include "selector.hpp" 2 | 3 | #include 4 | 5 | void Selector::select() { 6 | std::unique_lock lock(mtx); 7 | 8 | while (!stop_requested()) { 9 | // Wait until a channel has data available or a stop is requested. 10 | cv.wait(lock, [this] { 11 | return stop_requested() || 12 | std::any_of(channels.begin(), channels.end(), 13 | [](const auto& ch) { return ch(); }); 14 | }); 15 | 16 | // Process all channels that have data available 17 | for (auto ch_it = channels.begin(); ch_it != channels.end();) { 18 | if ((*ch_it)()) { 19 | // Data processed, remove this channel from the list 20 | ch_it = channels.erase(ch_it); 21 | } else { 22 | ++ch_it; 23 | } 24 | } 25 | 26 | if (channels.empty()) { 27 | break; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel/selector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class Channel; 11 | 12 | /** 13 | * @brief A Selector class for non-blocking channel operations. 14 | * 15 | * The Selector class allows multiple Channel objects to be monitored 16 | * simultaneously for incoming messages. It provides a select() method that 17 | * blocks until a message is received on any of the registered channels. 18 | * 19 | */ 20 | class Selector { 21 | public: 22 | Selector() : stop_flag_(false) {} 23 | // Disable copying and moving 24 | Selector(const Selector&) = delete; 25 | Selector& operator=(const Selector&) = delete; 26 | Selector(Selector&&) = delete; 27 | Selector& operator=(Selector&&) = delete; 28 | 29 | // Destructor 30 | ~Selector() = default; 31 | 32 | /** 33 | * @brief Adds a channel to the selector for receiving messages. 34 | * 35 | * @tparam T the type of the channel. 36 | * @param ch The channel to add. 37 | * @param callback The callback function to call when a message is received. 38 | * 39 | * Use Case: Add a channel to the selector and specify a callback function 40 | * to be called when a message is received. 41 | */ 42 | template 43 | void add_receive(Channel& ch, std::function callback); 44 | 45 | /** 46 | * @brief Continuously processes events on registered channels until 47 | * signaled to stop. 48 | * 49 | * This method runs a loop that: 50 | * 1. Waits for data to become available on any channel or for a stop 51 | * signal. 52 | * 2. Processes all available data once woken up. 53 | * 3. Removes channels that have been processed. 54 | * The loop continues until either all channels are processed or a stop is 55 | * requested. 56 | * 57 | * Usage example: 58 | * @code 59 | * std::thread selector_thread([&]() { 60 | * selector.select(); 61 | * }); 62 | * 63 | * // Do other work... 64 | * 65 | * selector.stop(); 66 | * @endcode 67 | */ 68 | void select(); 69 | 70 | /** 71 | * @brief Stops the select operation and unblocks the select() method. 72 | * 73 | * Use Case: Stop the selector and unblock the select() method. 74 | * Example: selector.stop(); 75 | */ 76 | void stop() { 77 | stop_flag_.test_and_set(std::memory_order_relaxed); 78 | notify(); 79 | } 80 | 81 | /** 82 | * @brief Notifies the selector that data may be available on the channels. 83 | * 84 | * Use Case: Notify the selector that data may be available on the channels. 85 | * Example: selector.notify(); 86 | * @note This function is meant for internal use and should not be called 87 | * directly (unless you know what you're doing). 88 | */ 89 | void notify() { cv.notify_all(); } 90 | 91 | private: 92 | /** 93 | * @brief Checks if a stop has been requested. 94 | * 95 | * @return true 96 | * @return false 97 | */ 98 | bool stop_requested() const { 99 | return stop_flag_.test(std::memory_order_relaxed); 100 | } 101 | 102 | std::vector> channels; 103 | std::atomic_flag stop_flag_; 104 | std::mutex mtx; 105 | std::condition_variable cv; 106 | }; 107 | 108 | template 109 | void Selector::add_receive(Channel& ch, std::function callback) { 110 | std::unique_lock lock(mtx); 111 | ch.register_selector(this); 112 | // Add a lambda function to the channels list 113 | channels.push_back([&ch, callback = std::move(callback), this]() mutable { 114 | if (ch.is_closed()) { 115 | ch.unregister_selector(this); 116 | return true; // Signal that this channel is done 117 | } 118 | auto value = ch.try_receive(); 119 | if (value) { 120 | callback(*value); // Call the callback with the received value 121 | return false; 122 | } 123 | return false; 124 | }); 125 | } -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel_2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_channel_2) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | blocking_iterator.hpp 10 | channel.hpp 11 | main.cpp 12 | ) 13 | 14 | set_target_properties( 15 | ${PROJECT_NAME} 16 | PROPERTIES 17 | CXX_STANDARD 20 18 | CXX_STANDARD_REQUIRED YES 19 | CXX_EXTENSIONS NO 20 | ) 21 | 22 | target_compile_options( 23 | ${PROJECT_NAME} 24 | PRIVATE 25 | $<$:/W4 /WX> 26 | $<$>:-Wall -Wextra -Wpedantic> 27 | ) 28 | 29 | if (USE_TSAN) 30 | target_compile_options( 31 | ${PROJECT_NAME} 32 | PRIVATE 33 | $<$:-fsanitize=thread> 34 | ) 35 | 36 | target_link_libraries( 37 | ${PROJECT_NAME} 38 | PRIVATE 39 | $<$:-fsanitize=thread> 40 | ) 41 | endif() 42 | 43 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel_2/blocking_iterator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ 4 | #define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ 5 | 6 | #include 7 | #include 8 | 9 | namespace msd { 10 | 11 | /** 12 | * @brief An iterator that block the current thread, 13 | * waiting to fetch elements from the channel. 14 | * 15 | * Used to implement channel range-based for loop. 16 | * 17 | * @tparam Channel Instance of channel. 18 | */ 19 | template 20 | class blocking_iterator { 21 | public: 22 | using value_type = typename Channel::value_type; 23 | using reference = const typename Channel::value_type&; 24 | 25 | explicit blocking_iterator(Channel& chan) : chan_{chan} {} 26 | 27 | /** 28 | * Advances to next element in the channel. 29 | */ 30 | blocking_iterator operator++() const noexcept { return *this; } 31 | 32 | /** 33 | * Returns an element from the channel. 34 | */ 35 | reference operator*() { 36 | chan_ >> value_; 37 | 38 | return value_; 39 | } 40 | 41 | /** 42 | * Makes iteration continue until the channel is closed and empty. 43 | */ 44 | bool operator!=(blocking_iterator) const { 45 | std::unique_lock lock{chan_.mtx_}; 46 | chan_.waitBeforeRead(lock); 47 | 48 | return !(chan_.closed() && chan_.empty()); 49 | } 50 | 51 | private: 52 | Channel& chan_; 53 | value_type value_{}; 54 | }; 55 | 56 | } // namespace msd 57 | 58 | /** 59 | * @brief Input iterator specialization 60 | */ 61 | template 62 | struct std::iterator_traits> { 63 | using value_type = typename msd::blocking_iterator::value_type; 64 | using reference = typename msd::blocking_iterator::reference; 65 | using iterator_category = std::input_iterator_tag; 66 | }; 67 | 68 | #endif // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel_2/channel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_HPP_ 4 | #define MSD_CHANNEL_HPP_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "blocking_iterator.hpp" 16 | 17 | namespace msd { 18 | 19 | #if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) 20 | #define NODISCARD [[nodiscard]] 21 | #else 22 | #define NODISCARD 23 | #endif 24 | 25 | /** 26 | * @brief Exception thrown if trying to write on closed channel. 27 | */ 28 | class closed_channel : public std::runtime_error { 29 | public: 30 | explicit closed_channel(const char* msg) : std::runtime_error{msg} {} 31 | }; 32 | 33 | /** 34 | * @brief Thread-safe container for sharing data between threads. 35 | * 36 | * Implements a blocking input iterator. 37 | * 38 | * @tparam T The type of the elements. 39 | */ 40 | template 41 | class channel { 42 | public: 43 | using value_type = T; 44 | using iterator = blocking_iterator>; 45 | using size_type = std::size_t; 46 | 47 | /** 48 | * Creates an unbuffered channel. 49 | */ 50 | constexpr channel() = default; 51 | 52 | /** 53 | * Creates a buffered channel. 54 | * 55 | * @param capacity Number of elements the channel can store before blocking. 56 | */ 57 | explicit constexpr channel(size_type capacity); 58 | 59 | /** 60 | * Pushes an element into the channel. 61 | * 62 | * @throws closed_channel if channel is closed. 63 | */ 64 | template 65 | friend channel::type>& operator<<( 66 | channel::type>&, Type&&); 67 | 68 | /** 69 | * Pops an element from the channel. 70 | * 71 | * @tparam Type The type of the elements 72 | */ 73 | template 74 | friend channel& operator>>(channel&, Type&); 75 | 76 | /** 77 | * Returns the number of elements in the channel. 78 | */ 79 | NODISCARD inline size_type constexpr size() const noexcept; 80 | 81 | /** 82 | * Returns true if there are no elements in channel. 83 | */ 84 | NODISCARD inline bool constexpr empty() const noexcept; 85 | 86 | /** 87 | * Closes the channel. 88 | */ 89 | inline void close() noexcept; 90 | 91 | /** 92 | * Returns true if the channel is closed. 93 | */ 94 | NODISCARD inline bool closed() const noexcept; 95 | 96 | /** 97 | * Iterator 98 | */ 99 | iterator begin() noexcept; 100 | iterator end() noexcept; 101 | 102 | /** 103 | * Channel cannot be copied or moved. 104 | */ 105 | channel(const channel&) = delete; 106 | channel& operator=(const channel&) = delete; 107 | channel(channel&&) = delete; 108 | channel& operator=(channel&&) = delete; 109 | virtual ~channel() = default; 110 | 111 | private: 112 | const size_type cap_{0}; 113 | std::queue queue_; 114 | std::atomic size_{0}; 115 | std::mutex mtx_; 116 | std::condition_variable cnd_; 117 | std::atomic is_closed_{false}; 118 | 119 | inline void waitBeforeRead(std::unique_lock&); 120 | inline void waitBeforeWrite(std::unique_lock&); 121 | friend class blocking_iterator; 122 | }; 123 | 124 | } // namespace msd 125 | 126 | #include "channel.inl" 127 | 128 | #endif // MSD_CHANNEL_HPP_ -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel_2/channel.inl: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Andrei Avram 2 | 3 | namespace msd { 4 | 5 | template 6 | constexpr channel::channel(const size_type capacity) : cap_{capacity} {} 7 | 8 | template 9 | channel::type>& operator<<( 10 | channel::type>& ch, T&& in) { 11 | { 12 | std::unique_lock lock{ch.mtx_}; 13 | if (ch.closed()) { 14 | throw closed_channel{"cannot write on closed channel"}; 15 | } 16 | } 17 | 18 | { 19 | std::unique_lock lock{ch.mtx_}; 20 | ch.waitBeforeWrite(lock); 21 | 22 | ch.queue_.push(std::forward(in)); 23 | ++ch.size_; 24 | } 25 | 26 | ch.cnd_.notify_one(); 27 | 28 | return ch; 29 | } 30 | 31 | template 32 | channel& operator>>(channel& ch, T& out) { 33 | if (ch.closed() && ch.empty()) { 34 | return ch; 35 | } 36 | 37 | { 38 | std::unique_lock lock{ch.mtx_}; 39 | ch.waitBeforeRead(lock); 40 | 41 | if (!ch.empty()) { 42 | out = std::move(ch.queue_.front()); 43 | ch.queue_.pop(); 44 | --ch.size_; 45 | } 46 | } 47 | 48 | ch.cnd_.notify_one(); 49 | 50 | return ch; 51 | } 52 | 53 | template 54 | constexpr typename channel::size_type channel::size() const noexcept { 55 | return size_; 56 | } 57 | 58 | template 59 | constexpr bool channel::empty() const noexcept { 60 | return size_ == 0; 61 | } 62 | 63 | template 64 | void channel::close() noexcept { 65 | { 66 | std::unique_lock lock{mtx_}; 67 | is_closed_.store(true); 68 | } 69 | cnd_.notify_all(); 70 | } 71 | 72 | template 73 | bool channel::closed() const noexcept { 74 | return is_closed_.load(); 75 | } 76 | 77 | template 78 | blocking_iterator> channel::begin() noexcept { 79 | return blocking_iterator>{*this}; 80 | } 81 | 82 | template 83 | blocking_iterator> channel::end() noexcept { 84 | return blocking_iterator>{*this}; 85 | } 86 | 87 | template 88 | void channel::waitBeforeRead(std::unique_lock& lock) { 89 | cnd_.wait(lock, [this]() { return !empty() || closed(); }); 90 | } 91 | 92 | template 93 | void channel::waitBeforeWrite(std::unique_lock& lock) { 94 | if (cap_ > 0 && size_ == cap_) { 95 | cnd_.wait(lock, [this]() { return size_ < cap_; }); 96 | } 97 | } 98 | 99 | } // namespace msd -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_channel_2/main.cpp: -------------------------------------------------------------------------------- 1 | // https://blog.andreiavram.ro/cpp-channel-thread-safe-container-share-data-threads/ 2 | // https://github.com/andreiavrammsd/cpp-channel/ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "channel.hpp" 10 | 11 | constexpr int kMaxMessagesCount = 10; 12 | 13 | constexpr size_t kBufferSize = 3; 14 | 15 | std::atomic_uint messagesCount{0}; 16 | 17 | // printing only 18 | // thread sanitizer does not detect data race 19 | // valgrind detects data race 20 | std::mutex printMutex; 21 | 22 | struct Portion { 23 | unsigned int data{}; 24 | }; 25 | 26 | auto produce_next_portion() { 27 | return Portion{.data = messagesCount.fetch_add(1)}; 28 | } 29 | 30 | void process_portion_taken([[maybe_unused]] const Portion& portion) {} 31 | 32 | // Thread sanitizers detect unlock attempts without locks held 33 | msd::channel buffer{kBufferSize}; 34 | 35 | void producer() { 36 | for (int i = 0; i < kMaxMessagesCount; i++) { 37 | Portion portion = produce_next_portion(); 38 | 39 | // Printing only 40 | { 41 | std::lock_guard lock{printMutex}; 42 | std::cout << std::this_thread::get_id() << "\tProduced: " << portion.data 43 | << '\n'; 44 | } 45 | 46 | try { 47 | buffer << portion; 48 | } catch (msd::closed_channel& e) { 49 | return; 50 | } 51 | } 52 | } 53 | 54 | void consumer() { 55 | for (const auto& portion : buffer) { 56 | // Printing only 57 | { 58 | std::lock_guard lock{printMutex}; 59 | std::cout << std::this_thread::get_id() << "\tConsumed: " << portion.data 60 | << '\n'; 61 | } 62 | 63 | process_portion_taken(std::move(portion)); 64 | } 65 | } 66 | 67 | int main() { 68 | std::jthread t1{producer}; 69 | std::jthread t2{consumer}; 70 | std::jthread t3{producer}; 71 | std::jthread t4{consumer}; 72 | 73 | std::this_thread::sleep_for(std::chrono::seconds(2)); 74 | // Printing only 75 | { 76 | std::lock_guard lock{printMutex}; 77 | std::cout << "Close channel\n"; 78 | buffer.close(); 79 | } 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_monitor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_monitor) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_monitor/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | constexpr int kMaxMessagesCount = 10; 8 | 9 | constexpr int kBufferSize = 3; 10 | 11 | std::atomic_uint messagesCount{0}; 12 | 13 | struct Portion { 14 | unsigned int data{}; 15 | }; 16 | 17 | auto produce_next_portion() { 18 | return Portion{.data = messagesCount.fetch_add(1)}; 19 | } 20 | 21 | void process_portion_taken([[maybe_unused]] Portion&& portion) {} 22 | 23 | class BoundedBuffer { 24 | Portion buffer[kBufferSize]; // 0..kBufferSize-1 25 | unsigned head{0}, tail{0}; // 0..kBufferSize-1 26 | std::atomic count{0}; // 0..kBufferSize 27 | std::condition_variable nonempty, nonfull; 28 | std::mutex mtx; 29 | std::atomic_bool endSignal{false}; 30 | 31 | public: 32 | void append(Portion&& portion) { 33 | std::unique_lock lck{mtx}; 34 | nonfull.wait(lck, [&] { return !(kBufferSize == count) || endSignal; }); 35 | if (endSignal) { 36 | return; 37 | } 38 | 39 | // assert(count < kBufferSize); 40 | std::cout << std::this_thread::get_id() << "\tAppend: " << portion.data 41 | << '\n'; 42 | 43 | buffer[tail++] = std::move(portion); 44 | tail %= kBufferSize; 45 | 46 | ++count; 47 | 48 | nonempty.notify_one(); 49 | } 50 | 51 | std::optional remove() { 52 | std::unique_lock lck{mtx}; 53 | nonempty.wait(lck, [&] { return !(0 == count) || endSignal; }); 54 | if (endSignal) { 55 | return {}; 56 | } 57 | 58 | // assert(count <= kBufferSize); 59 | Portion portion = buffer[head++]; 60 | std::cout << std::this_thread::get_id() << "\tRemove: " << portion.data 61 | << '\n'; 62 | head %= kBufferSize; 63 | --count; 64 | 65 | nonfull.notify_one(); 66 | 67 | return portion; 68 | } 69 | 70 | void stop() { 71 | endSignal = true; 72 | nonempty.notify_all(); 73 | nonfull.notify_all(); 74 | } 75 | }; 76 | 77 | BoundedBuffer buffer; 78 | 79 | void producer() { 80 | for (int i = 0; i < kMaxMessagesCount; i++) { 81 | Portion portion = produce_next_portion(); 82 | buffer.append(std::move(portion)); 83 | } 84 | } 85 | 86 | void consumer() { 87 | for (;;) { 88 | auto portion = buffer.remove(); 89 | if (!portion) { 90 | return; 91 | } 92 | process_portion_taken(std::move(*portion)); 93 | } 94 | } 95 | 96 | int main() { 97 | std::jthread t1{producer}; 98 | std::jthread t2{consumer}; 99 | std::jthread t3{producer}; 100 | std::jthread t4{consumer}; 101 | 102 | std::this_thread::sleep_for(std::chrono::seconds(2)); 103 | buffer.stop(); 104 | 105 | return 0; 106 | } -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_single/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_single) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_single/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Bounded buffer solution for one producer and one consumer. 8 | 9 | constexpr int kMaxMessages = 10; 10 | 11 | std::atomic_uint messagesCount{0}; 12 | 13 | struct Message { 14 | unsigned int data{}; 15 | }; 16 | 17 | Message produceMessage() { return Message{.data = messagesCount.fetch_add(1)}; } 18 | 19 | void consumeMessage([[maybe_unused]] const Message& message) {} 20 | 21 | constexpr unsigned int N = 4; 22 | 23 | std::mutex bufferMutex; 24 | Message buffer[N]; 25 | 26 | std::atomic count{0}; 27 | 28 | void producer() { 29 | unsigned tail{0}; 30 | for (int i = 0; i < kMaxMessages; i++) { 31 | Message message = produceMessage(); 32 | 33 | while (N == count) 34 | ; // busy waiting 35 | 36 | { 37 | std::lock_guard lock{bufferMutex}; 38 | buffer[tail++] = message; 39 | printf("Produced: %d\n", message.data); 40 | } 41 | 42 | tail %= N; 43 | count.fetch_add(1, std::memory_order_relaxed); 44 | } 45 | } 46 | 47 | void consumer() { 48 | unsigned head{0}; 49 | for (int i = 0; i < kMaxMessages; i++) { 50 | while (0 == count) 51 | ; // busy waiting 52 | 53 | Message message; 54 | { 55 | std::lock_guard lock{bufferMutex}; 56 | message = buffer[head++]; 57 | printf("Consumed: %d\n", message.data); 58 | } 59 | 60 | head %= N; 61 | count.fetch_sub(1, std::memory_order_relaxed); 62 | consumeMessage(message); 63 | } 64 | } 65 | 66 | int main() { 67 | std::thread t1(producer); 68 | std::thread t2(consumer); 69 | 70 | t1.join(); 71 | t2.join(); 72 | 73 | return EXIT_SUCCESS; 74 | } 75 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_single_c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(producer_consumer_single LANGUAGES C) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.c 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | C_STANDARD 11 14 | C_STANDARD_REQUIRED YES 15 | C_EXTENSIONS NO 16 | LINKER_LANGUAGE C 17 | ) 18 | 19 | target_compile_options( 20 | ${PROJECT_NAME} 21 | PUBLIC 22 | -Wall -Wextra -Wpedantic 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /ProducerConsumer/producer_consumer_single_c/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Bounded buffer solution for one producer and one consumer. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BUFFER_SIZE 5 11 | #define MAX_ITEMS 20 12 | 13 | int buffer[BUFFER_SIZE]; 14 | int in = 0; 15 | int out = 0; 16 | int produced_count = 0; 17 | int consumed_count = 0; 18 | 19 | sem_t mutex; 20 | sem_t full; 21 | sem_t empty; 22 | 23 | void* producer(void* arg) { 24 | (void)arg; 25 | int item = 1; 26 | 27 | while (produced_count < MAX_ITEMS) { 28 | sem_wait(&empty); 29 | sem_wait(&mutex); 30 | 31 | buffer[in] = item; 32 | printf("%ld Produced: %d\n", pthread_self(), item); 33 | item++; 34 | in = (in + 1) % BUFFER_SIZE; 35 | 36 | produced_count++; 37 | 38 | sem_post(&mutex); 39 | sem_post(&full); 40 | } 41 | 42 | pthread_exit(NULL); 43 | } 44 | 45 | void* consumer(void* arg) { 46 | (void)arg; 47 | while (consumed_count < MAX_ITEMS) { 48 | sem_wait(&full); 49 | sem_wait(&mutex); 50 | 51 | int item = buffer[out]; 52 | printf("%ld Consumed: %d\n", pthread_self(), item); 53 | out = (out + 1) % BUFFER_SIZE; 54 | 55 | consumed_count++; 56 | 57 | sem_post(&mutex); 58 | sem_post(&empty); 59 | } 60 | 61 | pthread_exit(NULL); 62 | } 63 | 64 | int main() { 65 | pthread_t producerThread, consumerThread; 66 | 67 | sem_init(&mutex, 0, 1); 68 | sem_init(&full, 0, 0); 69 | sem_init(&empty, 0, BUFFER_SIZE); 70 | 71 | pthread_create(&producerThread, NULL, producer, NULL); 72 | pthread_create(&consumerThread, NULL, consumer, NULL); 73 | 74 | pthread_join(producerThread, NULL); 75 | pthread_join(consumerThread, NULL); 76 | 77 | sem_destroy(&mutex); 78 | sem_destroy(&full); 79 | sem_destroy(&empty); 80 | 81 | return 0; 82 | } -------------------------------------------------------------------------------- /ProducerConsumer/second_readers_writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(second_readers_writers) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/second_readers_writers/main.cpp: -------------------------------------------------------------------------------- 1 | // writers-preference: no writer, once added to the queue, shall be kept waiting 2 | // longer than absolutely necessary 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | constexpr int kMaxMessagesCount = 10; 12 | 13 | std::atomic_uint messagesCount{0}; 14 | 15 | std::binary_semaphore wmutex{1}; 16 | std::binary_semaphore rmutex{1}; 17 | std::binary_semaphore readTry{1}; 18 | std::binary_semaphore resource{1}; 19 | 20 | std::mutex readCountGuard; 21 | uint32_t readCount{0}; 22 | 23 | std::mutex writeCountGuard; 24 | uint32_t writeCount{0}; 25 | 26 | std::mutex dataGuard; 27 | std::queue data; 28 | 29 | void startWrite() { 30 | // reserve entry section for writers - avoids race conditions 31 | wmutex.acquire(); 32 | 33 | { 34 | std::lock_guard lock{writeCountGuard}; 35 | // report yourself as a writer entering 36 | writeCount++; 37 | // checks if you're first writer 38 | if (writeCount == 1) { 39 | // if you're first, then you must lock the readers out. Prevent them from 40 | // trying to enter CS 41 | readTry.acquire(); 42 | } 43 | } 44 | 45 | // release entry section 46 | wmutex.release(); 47 | 48 | // reserve the resource for yourself - prevents other writers from 49 | // simultaneously editing the shared resource 50 | resource.acquire(); 51 | } 52 | 53 | void endWrite() { 54 | // reserve exit section 55 | wmutex.acquire(); 56 | 57 | { 58 | std::lock_guard lock{writeCountGuard}; 59 | // indicate you're leaving 60 | writeCount--; 61 | // checks if you're the last writer 62 | if (writeCount == 0) { 63 | // if you're last writer, you must unlock the readers. Allows them to try 64 | // enter CS for reading 65 | readTry.release(); 66 | } 67 | } 68 | 69 | wmutex.release(); 70 | } 71 | 72 | void writer(int value) { 73 | startWrite(); 74 | 75 | { 76 | // Writing is done 77 | std::lock_guard lock{dataGuard}; 78 | std::cout << std::this_thread::get_id() << "\tWrite: " << value << '\n'; 79 | data.push(value); 80 | } 81 | resource.release(); 82 | 83 | endWrite(); 84 | } 85 | 86 | void startRead() { 87 | // Indicate a reader is trying to enter 88 | readTry.acquire(); 89 | // lock entry section to avoid race condition with other readers 90 | rmutex.acquire(); 91 | 92 | { 93 | std::lock_guard lock{readCountGuard}; 94 | // report yourself as a reader 95 | readCount++; 96 | // checks if you are first reader 97 | if (readCount == 1) { 98 | // if you are first reader, lock the resource 99 | resource.acquire(); 100 | } 101 | } 102 | 103 | // release entry section for other readers 104 | rmutex.release(); 105 | // indicate you are done trying to access the resource 106 | readTry.release(); 107 | } 108 | 109 | void endRead() { 110 | // reserve exit section - avoids race condition with readers 111 | rmutex.acquire(); 112 | 113 | { 114 | std::lock_guard lock{readCountGuard}; 115 | // indicate you're leaving 116 | readCount--; 117 | // checks if you are last reader leaving 118 | if (readCount == 0) { 119 | // if last, you must release the locked resource 120 | resource.release(); 121 | } 122 | } 123 | 124 | // release exit section for other readers 125 | rmutex.release(); 126 | } 127 | 128 | std::optional reader() { 129 | std::optional result; 130 | 131 | startRead(); 132 | 133 | { 134 | // Do the Reading 135 | std::lock_guard lock{dataGuard}; 136 | if (!data.empty()) { 137 | result = data.front(); 138 | data.pop(); 139 | std::cout << std::this_thread::get_id() << "\tRead: " << *result << '\n'; 140 | } else { 141 | std::cout << std::this_thread::get_id() << "\tRead done!\n"; 142 | } 143 | } 144 | 145 | endRead(); 146 | 147 | return result; 148 | } 149 | 150 | int main() { 151 | std::jthread t1{[]() { 152 | for (int i = 0; i < kMaxMessagesCount; i++) { 153 | writer(messagesCount.fetch_add(1)); 154 | } 155 | }}; 156 | 157 | std::jthread t2{[]() { 158 | while (auto result = reader()) { 159 | // Process result here 160 | }; 161 | }}; 162 | 163 | std::jthread t3{[]() { 164 | for (int i = 0; i < kMaxMessagesCount; i++) { 165 | writer(messagesCount.fetch_add(1)); 166 | } 167 | }}; 168 | 169 | std::jthread t4{[]() { 170 | while (auto result = reader()) { 171 | // Process result here 172 | }; 173 | }}; 174 | 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /ProducerConsumer/second_readers_writers_c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(second_readers_writers LANGUAGES C) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.c 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | C_STANDARD 11 14 | C_STANDARD_REQUIRED YES 15 | C_EXTENSIONS NO 16 | LINKER_LANGUAGE C 17 | ) 18 | 19 | target_compile_options( 20 | ${PROJECT_NAME} 21 | PUBLIC 22 | -Wall -Wextra -Wpedantic 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /ProducerConsumer/second_readers_writers_c/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | writers-preference: no writer, once added to the queue, shall be kept waiting 3 | longer than absolutely necessary 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int readcount = 0, writecount = 0, sh_var = 5; 11 | sem_t rcount_guard, wcount_guard, rsem, wsem, p_guard; 12 | pthread_t r[5], w[3]; 13 | 14 | void *reader(void *i) { 15 | sem_wait(&p_guard); 16 | printf("-------------------------\n"); 17 | printf("%ld reader-%ld is reading\n", pthread_self(), (long)i); 18 | sem_post(&p_guard); 19 | 20 | sem_wait(&rsem); 21 | 22 | sem_wait(&rcount_guard); 23 | readcount++; 24 | if (readcount == 1) sem_wait(&wsem); 25 | sem_post(&rcount_guard); 26 | 27 | sem_post(&rsem); 28 | 29 | sem_wait(&p_guard); 30 | printf("%ld updated value :%d\n", pthread_self(), sh_var); 31 | sem_post(&p_guard); 32 | 33 | sem_wait(&rcount_guard); 34 | readcount--; 35 | if (readcount == 0) sem_post(&wsem); 36 | sem_post(&rcount_guard); 37 | 38 | return NULL; 39 | } 40 | 41 | void *writer(void *i) { 42 | sem_wait(&p_guard); 43 | printf("\n%ld writer-%ld is writing\n", pthread_self(), (long)i); 44 | sem_post(&p_guard); 45 | 46 | sem_wait(&wcount_guard); 47 | writecount++; 48 | if (writecount == 1) sem_wait(&rsem); 49 | sem_post(&wcount_guard); 50 | 51 | sem_wait(&wsem); 52 | sh_var = sh_var + 5; 53 | sem_post(&wsem); 54 | 55 | sem_wait(&wcount_guard); 56 | writecount--; 57 | if (writecount == 0) sem_post(&rsem); 58 | sem_post(&wcount_guard); 59 | 60 | return NULL; 61 | } 62 | 63 | int main() { 64 | sem_init(&rcount_guard, 0, 1); 65 | sem_init(&wsem, 0, 1); 66 | sem_init(&wcount_guard, 0, 1); 67 | sem_init(&rsem, 0, 1); 68 | sem_init(&p_guard, 0, 1); 69 | 70 | pthread_create(&r[0], NULL, reader, (void *)0); 71 | pthread_create(&w[0], NULL, writer, (void *)0); 72 | pthread_create(&r[1], NULL, reader, (void *)1); 73 | pthread_create(&r[2], NULL, reader, (void *)2); 74 | pthread_create(&r[3], NULL, reader, (void *)3); 75 | pthread_create(&w[1], NULL, writer, (void *)1); 76 | pthread_create(&r[4], NULL, reader, (void *)4); 77 | 78 | pthread_join(r[0], NULL); 79 | pthread_join(w[0], NULL); 80 | pthread_join(r[1], NULL); 81 | pthread_join(r[2], NULL); 82 | pthread_join(r[3], NULL); 83 | pthread_join(w[1], NULL); 84 | pthread_join(r[4], NULL); 85 | 86 | sem_destroy(&rcount_guard); 87 | sem_destroy(&wsem); 88 | sem_destroy(&wcount_guard); 89 | sem_destroy(&rsem); 90 | sem_destroy(&p_guard); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /ProducerConsumer/simplest_readers_writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(simplest_readers_writers) 4 | 5 | add_executable( 6 | ${PROJECT_NAME} 7 | main.cpp 8 | ) 9 | 10 | set_target_properties( 11 | ${PROJECT_NAME} 12 | PROPERTIES 13 | CXX_STANDARD 20 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO 16 | ) 17 | 18 | target_compile_options( 19 | ${PROJECT_NAME} 20 | PRIVATE 21 | $<$:/W4 /WX> 22 | $<$>:-Wall -Wextra -Wpedantic> 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /ProducerConsumer/simplest_readers_writers/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | constexpr int kMaxMessagesCount = 10; 10 | 11 | std::atomic_uint messagesCount{0}; 12 | 13 | std::atomic_bool endSignal{false}; 14 | 15 | std::condition_variable readerCond; 16 | 17 | std::mutex dataGuard; 18 | std::queue data; 19 | 20 | void writer(int value) { 21 | std::unique_lock lock{dataGuard}; 22 | std::cout << std::this_thread::get_id() << "\tWrite: " << value << '\n'; 23 | data.push(value); 24 | readerCond.notify_one(); 25 | } 26 | 27 | std::optional reader() { 28 | std::optional result; 29 | 30 | { 31 | std::unique_lock lock{dataGuard}; 32 | readerCond.wait(lock, []() { return !data.empty() || endSignal; }); 33 | if (!data.empty()) { 34 | result = data.front(); 35 | data.pop(); 36 | std::cout << std::this_thread::get_id() << "\tRead: " << *result << '\n'; 37 | } else { 38 | std::cout << std::this_thread::get_id() << "\tRead done!\n"; 39 | } 40 | } 41 | 42 | return result; 43 | } 44 | 45 | int main() { 46 | std::jthread t1{[]() { 47 | for (int i = 0; i < kMaxMessagesCount; i++) { 48 | writer(messagesCount.fetch_add(1)); 49 | } 50 | }}; 51 | 52 | std::jthread t2{[]() { 53 | while (auto result = reader()) { 54 | // Process result here 55 | }; 56 | }}; 57 | 58 | std::jthread t3{[]() { 59 | for (int i = 0; i < kMaxMessagesCount; i++) { 60 | writer(messagesCount.fetch_add(1)); 61 | } 62 | }}; 63 | 64 | std::jthread t4{[]() { 65 | while (auto result = reader()) { 66 | // Process result here 67 | }; 68 | }}; 69 | 70 | endSignal = true; 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /ProducerConsumer/third_readers_writers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(third_readers_writers) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | -------------------------------------------------------------------------------- /ProducerConsumer/third_readers_writers/main.cpp: -------------------------------------------------------------------------------- 1 | // no thread shall be allowed to starve 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | constexpr int kMaxMessagesCount = 10; 11 | 12 | std::atomic_uint messagesCount{0}; 13 | 14 | // controls access (read/write) to the resource. 15 | std::binary_semaphore resource{1}; 16 | 17 | // for syncing changes to shared variable readcount 18 | std::binary_semaphore rmutex{1}; 19 | 20 | // FAIRNESS: preserves ordering of requests (signaling must be FIFO) 21 | std::binary_semaphore serviceQueue{1}; 22 | 23 | std::mutex readCountGuard; 24 | uint32_t readCount{0}; 25 | 26 | // Can be replaced with std::binary_semaphore dataGuard{1} 27 | // thread sanitizer does not detect data race 28 | // valgrind detects data race 29 | std::mutex dataGuard; 30 | std::queue data; 31 | 32 | void startWrite() { 33 | // wait in line to be serviced 34 | serviceQueue.acquire(); 35 | // request exclusive access to resource 36 | resource.acquire(); 37 | // let next in line be serviced 38 | serviceQueue.release(); 39 | } 40 | 41 | void endWrite() { 42 | // release resource access for next reader/writer 43 | resource.release(); 44 | } 45 | 46 | void writer(int value) { 47 | startWrite(); 48 | 49 | { 50 | // Writing is done 51 | std::lock_guard lock{dataGuard}; 52 | std::cout << std::this_thread::get_id() << "\tWrite: " << value << '\n'; 53 | data.push(value); 54 | } 55 | 56 | endWrite(); 57 | } 58 | 59 | void startRead() { 60 | // wait in line to be serviced 61 | serviceQueue.acquire(); 62 | // request exclusive access to readcount 63 | rmutex.acquire(); 64 | 65 | { 66 | std::lock_guard lock{readCountGuard}; 67 | // update count of active readers 68 | readCount++; 69 | // if I am the first reader 70 | if (readCount == 1) { 71 | // request resource access for readers (writers blocked) 72 | resource.acquire(); 73 | } 74 | } 75 | 76 | // let next in line be serviced 77 | serviceQueue.release(); 78 | // release access to readcount 79 | rmutex.release(); 80 | } 81 | 82 | void endRead() { 83 | // request exclusive access to readcount 84 | rmutex.acquire(); 85 | 86 | { 87 | std::lock_guard lock{readCountGuard}; 88 | // update count of active readers 89 | readCount--; 90 | // if there are no readers left 91 | if (readCount == 0) { 92 | // release resource access for all 93 | resource.release(); 94 | } 95 | } 96 | 97 | // release access to readcount 98 | rmutex.release(); 99 | } 100 | 101 | std::optional reader() { 102 | std::optional result; 103 | 104 | startRead(); 105 | 106 | { 107 | // Do the Reading 108 | std::lock_guard lock{dataGuard}; 109 | if (!data.empty()) { 110 | result = data.front(); 111 | data.pop(); 112 | std::cout << std::this_thread::get_id() << "\tRead: " << *result << '\n'; 113 | } else { 114 | std::cout << std::this_thread::get_id() << "\tRead done!\n"; 115 | } 116 | } 117 | 118 | endRead(); 119 | 120 | return result; 121 | } 122 | 123 | int main() { 124 | std::jthread t1{[]() { 125 | for (int i = 0; i < kMaxMessagesCount; i++) { 126 | writer(messagesCount.fetch_add(1)); 127 | } 128 | }}; 129 | 130 | std::jthread t2{[]() { 131 | while (auto result = reader()) { 132 | // Process result here 133 | }; 134 | }}; 135 | 136 | std::jthread t3{[]() { 137 | for (int i = 0; i < kMaxMessagesCount; i++) { 138 | writer(messagesCount.fetch_add(1)); 139 | } 140 | }}; 141 | 142 | std::jthread t4{[]() { 143 | while (auto result = reader()) { 144 | // Process result here 145 | }; 146 | }}; 147 | 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Concurrency Patterns 2 | 3 | ## Synchronization Patterns 4 | 5 | ### Dealing with Sharing 6 | * Copied Value 7 | * Thread-Specific Storage 8 | * Future 9 | 10 | ### Dealing with Mutation 11 | * Scoped Locking 12 | * Strategized Locking 13 | * Thread-Safe Interface 14 | * Guarded Suspension 15 | 16 | ## Concurrent Architecture 17 | * Active Object 18 | * Monitor Object 19 | * Reactor 20 | 21 | ## Others 22 | * Event Loop 23 | * Producer/Consumer 24 | * Spinlock 25 | * Seqlock 26 | * LockFree 27 | 28 | ## References 29 | * [Concurrency Patterns - Rainer Grimm - CppCon 2021](https://www.youtube.com/watch?v=A3DQxZCtKqo) 30 | * [Pattern-Oriented Software Architecture](https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture) 31 | * [Concurrency with Modern C++](https://leanpub.com/concurrencywithmodernc/c/RkLJ8CTGGIo2) 32 | * [Idiomatic Event Loop in C++](https://habr.com/en/post/665730/) 33 | -------------------------------------------------------------------------------- /Reactor/Cpp11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ReactorCpp11) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | reactor.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /Reactor/Cpp11/README.md: -------------------------------------------------------------------------------- 1 | # Links 2 | * [Reactor pattern in modern C++. Please comment on coding improvements to be done](https://codereview.stackexchange.com/questions/249607/reactor-pattern-in-modern-c-please-comment-on-coding-improvements-to-be-done) -------------------------------------------------------------------------------- /Reactor/Cpp11queue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ReactorCpp11queue) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | reactor.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /Reactor/Cpp11queue/README.md: -------------------------------------------------------------------------------- 1 | # Links 2 | * [Reactor pattern in modern C++. Please comment on coding improvements to be done](https://codereview.stackexchange.com/questions/249607/reactor-pattern-in-modern-c-please-comment-on-coding-improvements-to-be-done) -------------------------------------------------------------------------------- /Reactor/Cpp98/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ReactorCpp98) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | ClientHandler.cpp 10 | Dispatcher.h 11 | ListenHandler.cpp 12 | ClientHandler.h 13 | EpollPoller.cpp 14 | ListenHandler.h 15 | CMakeLists.txt 16 | EpollPoller.h 17 | main.cpp 18 | Dispatcher.cpp 19 | EventHandler.h 20 | Poller.h 21 | ) 22 | 23 | set_target_properties( 24 | ${PROJECT_NAME} 25 | PROPERTIES 26 | CXX_STANDARD 98 27 | CXX_STANDARD_REQUIRED YES 28 | CXX_EXTENSIONS NO 29 | ) 30 | 31 | target_link_libraries( 32 | ${PROJECT_NAME} 33 | PRIVATE 34 | Threads::Threads 35 | ) 36 | 37 | # Test client 38 | add_executable( 39 | test_client 40 | main_client.cpp 41 | ) 42 | 43 | set_target_properties( 44 | test_client 45 | PROPERTIES 46 | CXX_STANDARD 98 47 | CXX_STANDARD_REQUIRED YES 48 | CXX_EXTENSIONS NO 49 | ) 50 | 51 | target_link_libraries( 52 | test_client 53 | PRIVATE 54 | Threads::Threads 55 | ) 56 | -------------------------------------------------------------------------------- /Reactor/Cpp98/ClientHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "ClientHandler.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | ClientHandler::ClientHandler(Handler fd) : clientFd(fd) { 12 | memset(revBuff, 0, sizeof(revBuff)); 13 | } 14 | 15 | ClientHandler::~ClientHandler() { close(clientFd); } 16 | 17 | void ClientHandler::handleRead() { 18 | if (read(clientFd, revBuff, sizeof(revBuff))) { 19 | std::cout << "recv client:" << clientFd << ":" << revBuff << std::endl; 20 | write(clientFd, revBuff, strlen(revBuff)); 21 | memset(revBuff, 0, sizeof(revBuff)); 22 | } else { 23 | std::cout << "client close fd:" << clientFd << std::endl; 24 | close(clientFd); 25 | } 26 | } 27 | 28 | void ClientHandler::handleWirte() { 29 | // nothing todo 30 | } 31 | 32 | void ClientHandler::handleError() { 33 | // nothing todo 34 | std::cout << "client close:" << clientFd << std::endl; 35 | } -------------------------------------------------------------------------------- /Reactor/Cpp98/ClientHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_HANDLER_H_ 2 | #define CLIENT_HANDLER_H_ 3 | 4 | #include "EventHandler.h" 5 | 6 | class ClientHandler : public EventHandler { 7 | public: 8 | ClientHandler(Handler fd); 9 | ~ClientHandler(); 10 | Handler getHandler() { return clientFd; } 11 | virtual void handleRead(); 12 | virtual void handleWirte(); 13 | virtual void handleError(); 14 | 15 | private: 16 | Handler clientFd; 17 | char revBuff[1024]; 18 | }; 19 | 20 | #endif // !CLIENT_HANDLER_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/Dispatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "Dispatcher.h" 2 | 3 | #include 4 | 5 | #include "EpollPoller.h" 6 | 7 | Dispatcher* Dispatcher::instance = new Dispatcher(); 8 | 9 | Dispatcher::Dispatcher() { 10 | _poller = new (std::nothrow) epollPoller(); 11 | assert(NULL != _poller); 12 | } 13 | 14 | Dispatcher::~Dispatcher() { 15 | std::map::iterator iter = _handlers.begin(); 16 | for (; iter != _handlers.end(); ++iter) { 17 | delete iter->second; 18 | } 19 | 20 | if (_poller) delete _poller; 21 | } 22 | 23 | int Dispatcher::registHandler(EventHandler* handler) { 24 | Handler h = handler->getHandler(); 25 | if (_handlers.end() == _handlers.find(h)) { 26 | _handlers.insert(std::make_pair(h, handler)); 27 | } 28 | return _poller->regist(h); 29 | } 30 | 31 | void Dispatcher::removeHander(EventHandler* handler) { 32 | Handler h = handler->getHandler(); 33 | std::map::iterator iter = _handlers.find(h); 34 | if (iter != _handlers.end()) { 35 | delete iter->second; 36 | _handlers.erase(iter); 37 | } 38 | _poller->remove(h); 39 | } 40 | 41 | void Dispatcher::dispatchEvent(int timeout) { 42 | _poller->waitEvent(_handlers, timeout); 43 | } -------------------------------------------------------------------------------- /Reactor/Cpp98/Dispatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPATHER_H_H_ 2 | #define DISPATHER_H_H_ 3 | #include 4 | 5 | #include "EventHandler.h" 6 | #include "Poller.h" 7 | 8 | class Dispatcher { 9 | public: 10 | static Dispatcher* getInstance() { return instance; } 11 | int registHandler(EventHandler* handler); 12 | void removeHander(EventHandler* handler); 13 | void dispatchEvent(int timeout = 0); 14 | 15 | private: 16 | //单例模式 17 | Dispatcher(); 18 | ~Dispatcher(); 19 | //只声明不实现,用于禁止拷贝构造和赋值构造 20 | Dispatcher(const Dispatcher&); 21 | Dispatcher& operator=(const Dispatcher&); 22 | 23 | private: 24 | Poller* _poller; 25 | std::map _handlers; 26 | 27 | private: 28 | static Dispatcher* instance; 29 | }; 30 | 31 | #endif // !DISPATHER_H_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/EpollPoller.cpp: -------------------------------------------------------------------------------- 1 | #include "EpollPoller.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | typedef std::vector EventList; 9 | epollPoller::epollPoller() : _events(16), epollFd(::epoll_create(100000)) {} 10 | 11 | epollPoller::~epollPoller() { close(epollFd); } 12 | 13 | int epollPoller::waitEvent(std::map& handlers, 14 | int timeout) { 15 | std::cout << "start wait" << std::endl; 16 | int nready = 17 | epoll_wait(epollFd, &_events[0], static_cast(_events.size()), -1); 18 | if (-1 == nready) { 19 | std::cout << "WARNING: epoll_wait error " << errno << std::endl; 20 | return -1; 21 | } 22 | 23 | for (int i = 0; i < nready; i++) { 24 | Handler handle = _events[i].data.fd; 25 | if ((EPOLLHUP | EPOLLERR) & _events[i].events) { 26 | assert(handlers[handle] != NULL); 27 | (handlers[handle])->handleError(); 28 | } else { 29 | if ((EPOLLIN)&_events[i].events) { 30 | assert(handlers[handle] != NULL); 31 | (handlers[handle])->handleRead(); 32 | } 33 | 34 | if (EPOLLOUT & _events[i].events) { 35 | (handlers[handle])->handleWirte(); 36 | } 37 | } 38 | } 39 | if (static_cast(nready) == _events.size()) { 40 | _events.resize(_events.size() * 2); 41 | } 42 | return nready; 43 | } 44 | 45 | int epollPoller::regist(Handler handler) { 46 | struct epoll_event ev; 47 | ev.data.fd = handler; 48 | ev.events |= EPOLLIN; 49 | ev.events |= EPOLLET; 50 | 51 | if (epoll_ctl(epollFd, EPOLL_CTL_ADD, handler, &ev) != 0) { 52 | if (errno == ENOENT) { 53 | if (epoll_ctl(epollFd, EPOLL_CTL_ADD, handler, &ev) != 0) { 54 | std::cout << "epoll_ctl add error " << errno << std::endl; 55 | return -1; 56 | } 57 | } 58 | } 59 | return 0; 60 | } 61 | 62 | int epollPoller::remove(Handler handler) { 63 | struct epoll_event ev; 64 | if (epoll_ctl(epollFd, EPOLL_CTL_DEL, handler, &ev) != 0) { 65 | std::cout << "epoll_ctl del error" << errno << std::endl; 66 | return -1; 67 | } 68 | return 0; 69 | } -------------------------------------------------------------------------------- /Reactor/Cpp98/EpollPoller.h: -------------------------------------------------------------------------------- 1 | #ifndef EPOLL_POLLER_H_ 2 | #define EPOLL_POLLER_H_ 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Poller.h" 9 | 10 | class epollPoller : public Poller { 11 | public: 12 | epollPoller(); 13 | ~epollPoller(); 14 | int waitEvent(std::map& handlers, int timeout = 0); 15 | int regist(Handler handler); 16 | int remove(Handler handler); 17 | 18 | private: 19 | typedef std::vector EventList; 20 | EventList _events; 21 | int epollFd; 22 | }; 23 | 24 | #endif // !EPOLL_POLLER_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/EventHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef EVENT_HANDLER_H_ 2 | #define EVENT_HANDLER_H_ 3 | 4 | typedef int Handler; 5 | 6 | class EventHandler { 7 | public: 8 | EventHandler() {} 9 | virtual ~EventHandler() {} 10 | virtual Handler getHandler() = 0; 11 | virtual void handleRead() = 0; 12 | virtual void handleWirte() = 0; 13 | virtual void handleError() = 0; 14 | }; 15 | 16 | #endif // !EVENT_HANDLER_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/ListenHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "ListenHandler.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "ClientHandler.h" 13 | #include "Dispatcher.h" 14 | 15 | ListenHandler::ListenHandler(Handler fd) : listenFd(fd) {} 16 | 17 | ListenHandler::~ListenHandler() { close(listenFd); } 18 | 19 | void ListenHandler::handleRead() { 20 | struct sockaddr_in peeraddr; 21 | socklen_t peerlen = sizeof(sockaddr_in); 22 | int connFd = ::accept(listenFd, (sockaddr*)&peeraddr, &peerlen); 23 | if (-1 == connFd) { 24 | return; 25 | } 26 | 27 | std::cout << "ip=" << inet_ntoa(peeraddr.sin_addr) 28 | << " port=" << ntohs(peeraddr.sin_port) << " fd:" << connFd 29 | << std::endl; 30 | 31 | EventHandler* h = new (std::nothrow) ClientHandler(connFd); 32 | assert(NULL != h); 33 | Dispatcher::getInstance()->registHandler(h); 34 | } 35 | 36 | void ListenHandler::handleWirte() { 37 | // nothing todo 38 | } 39 | 40 | void ListenHandler::handleError() { 41 | // nothing todo 42 | } -------------------------------------------------------------------------------- /Reactor/Cpp98/ListenHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTEN_HANDLER_H_ 2 | #define LISTEN_HANDLER_H_ 3 | 4 | #include "EventHandler.h" 5 | 6 | class ListenHandler : public EventHandler { 7 | public: 8 | ListenHandler(Handler fd); 9 | ~ListenHandler(); 10 | Handler getHandler() { return listenFd; } 11 | void handleRead(); 12 | void handleWirte(); 13 | void handleError(); 14 | 15 | private: 16 | Handler listenFd; 17 | }; 18 | 19 | #endif // !LISTEN_HANDLER_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/Poller.h: -------------------------------------------------------------------------------- 1 | #ifndef POLLER_H_H_ 2 | #define POLLER_H_H_ 3 | #include 4 | 5 | #include "EventHandler.h" 6 | 7 | class Poller { 8 | public: 9 | Poller() {} 10 | virtual ~Poller() {} 11 | virtual int waitEvent(std::map& handlers, 12 | int timeout = 0) = 0; 13 | virtual int regist(Handler handler) = 0; 14 | virtual int remove(Handler handler) = 0; 15 | 16 | private: 17 | }; 18 | 19 | #endif // !POLLER_H_H_ -------------------------------------------------------------------------------- /Reactor/Cpp98/README.md: -------------------------------------------------------------------------------- 1 | ## Links 2 | * [Design pattern of Reactor reactor](https://www.codetd.com/en/article/11799588) 3 | -------------------------------------------------------------------------------- /Reactor/Cpp98/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "Dispatcher.h" 11 | #include "ListenHandler.h" 12 | 13 | #define ERR_EXIT(m) \ 14 | do { \ 15 | perror(m); \ 16 | exit(EXIT_FAILURE); \ 17 | } while (0) 18 | 19 | int main() { 20 | int listenfd = socket(AF_INET, SOCK_STREAM, 0); 21 | if (listenfd < 0) ERR_EXIT("socket error"); 22 | 23 | int fd_flags = ::fcntl(listenfd, F_GETFL, 0); 24 | fd_flags |= FD_CLOEXEC; 25 | fd_flags |= O_NONBLOCK; 26 | fcntl(listenfd, F_SETFD, fd_flags); 27 | 28 | struct sockaddr_in seraddr; 29 | seraddr.sin_family = AF_INET; 30 | seraddr.sin_port = htons(5188); // Same as client 31 | seraddr.sin_addr.s_addr = htonl(INADDR_ANY); 32 | 33 | int on = 1; 34 | if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 35 | ERR_EXIT("setsockopt"); 36 | 37 | if (bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) < 0) 38 | ERR_EXIT("bind"); 39 | 40 | if (listen(listenfd, 5) < 0) ERR_EXIT("listen"); 41 | 42 | EventHandler* handler = new ListenHandler(listenfd); 43 | Dispatcher::getInstance()->registHandler(handler); 44 | 45 | while (true) { 46 | Dispatcher::getInstance()->dispatchEvent(1000); 47 | } 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /Reactor/Cpp98/main_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | const char DATA[] = "Test message"; 11 | 12 | /* 13 | * This program creates a socket and initiates a connection with 14 | * the socket given in the command line. Some data are sent over the 15 | * connection and then the socket is closed, ending the connection. 16 | */ 17 | int main(int argc, char *argv[]) { 18 | int sock; 19 | struct sockaddr_in server; 20 | char buf[1024]; 21 | 22 | /* Create socket. */ 23 | sock = socket(AF_INET, SOCK_STREAM, 0); 24 | if (sock == -1) { 25 | perror("opening stream socket"); 26 | return EXIT_FAILURE; 27 | } 28 | 29 | struct hostent *hp; 30 | hp = gethostbyname("localhost"); 31 | memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); 32 | 33 | server.sin_family = AF_INET; 34 | server.sin_port = htons(5188); // Same as server 35 | 36 | if (connect(sock, (struct sockaddr *)&server, sizeof server) == -1) { 37 | perror("connecting stream socket"); 38 | return EXIT_FAILURE; 39 | } 40 | 41 | if (write(sock, DATA, sizeof DATA) == -1) perror("writing on stream socket"); 42 | close(sock); 43 | 44 | return EXIT_SUCCESS; 45 | } 46 | -------------------------------------------------------------------------------- /Reactor/Poco/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14.4) 2 | 3 | project(PocoReactor) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare( 7 | Poco 8 | URL https://github.com/pocoproject/poco/archive/refs/tags/poco-1.12.4-release.tar.gz 9 | ) 10 | FetchContent_MakeAvailable(Poco) 11 | 12 | add_executable( 13 | ${PROJECT_NAME} 14 | main.cpp 15 | ) 16 | target_link_libraries( 17 | ${PROJECT_NAME} 18 | PUBLIC 19 | Poco::Foundation 20 | Poco::Net 21 | Poco::Util 22 | ) -------------------------------------------------------------------------------- /Reactor/Poco/main.cpp: -------------------------------------------------------------------------------- 1 | // http://www.modernescpp.com/index.php/reactor 2 | // https://github.com/pocoproject/cmake-sample/blob/main/CMakeLists.txt 3 | 4 | #include 5 | #include 6 | 7 | #include "Poco/Net/ServerSocket.h" 8 | #include "Poco/Net/SocketAcceptor.h" 9 | #include "Poco/Net/SocketNotification.h" 10 | #include "Poco/Net/SocketReactor.h" 11 | #include "Poco/Net/StreamSocket.h" 12 | #include "Poco/Observer.h" 13 | #include "Poco/Thread.h" 14 | #include "Poco/Util/ServerApplication.h" 15 | 16 | using Poco::Observer; 17 | using Poco::Thread; 18 | 19 | using Poco::Net::ReadableNotification; 20 | using Poco::Net::ServerSocket; 21 | using Poco::Net::ShutdownNotification; 22 | using Poco::Net::SocketAcceptor; 23 | using Poco::Net::SocketReactor; 24 | using Poco::Net::StreamSocket; 25 | 26 | using Poco::Util::Application; 27 | 28 | class EchoHandler { 29 | public: 30 | EchoHandler(const StreamSocket& s, SocketReactor& r) : socket(s), reactor(r) { 31 | reactor.addEventHandler(socket, Observer( 32 | *this, &EchoHandler::socketReadable)); 33 | } 34 | 35 | void socketReadable(ReadableNotification*) { 36 | char buffer[8]; 37 | int n = socket.receiveBytes(buffer, sizeof(buffer)); 38 | if (n > 0) { 39 | socket.sendBytes(buffer, n); 40 | } else { 41 | reactor.removeEventHandler(socket, 42 | Observer( 43 | *this, &EchoHandler::socketReadable)); 44 | delete this; 45 | } 46 | } 47 | 48 | private: 49 | StreamSocket socket; 50 | SocketReactor& reactor; 51 | }; 52 | 53 | class DataHandler { 54 | public: 55 | DataHandler(StreamSocket& s, SocketReactor& r) 56 | : socket(s), reactor(r), outFile("reactorOutput.txt") { 57 | reactor.addEventHandler(socket, Observer( 58 | *this, &DataHandler::socketReadable)); 59 | reactor.addEventHandler(socket, Observer( 60 | *this, &DataHandler::socketShutdown)); 61 | socket.setBlocking(false); 62 | } 63 | 64 | ~DataHandler() { 65 | reactor.removeEventHandler(socket, 66 | Observer( 67 | *this, &DataHandler::socketReadable)); 68 | reactor.removeEventHandler(socket, 69 | Observer( 70 | *this, &DataHandler::socketShutdown)); 71 | } 72 | 73 | void socketReadable(ReadableNotification*) { 74 | char buffer[64]; 75 | int n = 0; 76 | do { 77 | n = socket.receiveBytes(&buffer[0], sizeof(buffer)); 78 | if (n > 0) { 79 | std::string s(buffer, n); 80 | outFile << s << std::flush; 81 | } else 82 | break; 83 | } while (true); 84 | } 85 | 86 | void socketShutdown(ShutdownNotification*) { delete this; } 87 | 88 | private: 89 | StreamSocket socket; 90 | SocketReactor& reactor; 91 | std::ofstream outFile; 92 | }; 93 | 94 | class Server : public Poco::Util::ServerApplication { 95 | protected: 96 | void initialize(Application& self) { ServerApplication::initialize(self); } 97 | 98 | void uninitialize() { ServerApplication::uninitialize(); } 99 | 100 | int main(const std::vector&) { 101 | ServerSocket serverSocketEcho(4711); 102 | ServerSocket serverSocketData(4712); 103 | SocketReactor reactor; 104 | SocketAcceptor acceptorEcho(serverSocketEcho, reactor); 105 | SocketAcceptor acceptorData(serverSocketData, reactor); 106 | Thread thread; 107 | thread.start(reactor); 108 | waitForTerminationRequest(); 109 | reactor.stop(); 110 | thread.join(); 111 | 112 | return Application::EXIT_OK; 113 | } 114 | }; 115 | 116 | int main(int argc, char** argv) { 117 | Server app; 118 | return app.run(argc, argv); 119 | } -------------------------------------------------------------------------------- /Reactor/README.md: -------------------------------------------------------------------------------- 1 | # Reactor 2 | 3 | * Concurrent Architecture Pattern 4 | 5 | * An event-driven framework to demultiplex and dispatch service requests concurrently onto various service providers. 6 | 7 | ## Summary 8 | 9 | * For each supported service type implement an event handler that fulfils the specific client request. 10 | 11 | * Register this service handler within the Reactor. 12 | 13 | * The Reactor uses an event demultiplexer to wait synchronously on all incoming events. 14 | 15 | * When an event arrives, the Reactor is notified and dispatches it to the specific service. 16 | 17 | 18 | ## Class Diagram 19 | ![](reactor.svg) 20 | 21 | 22 | ## Advantages 23 | 24 | * A clear separation of framework and application logic. 25 | 26 | * The Reactor can be ported to various platforms, because the underlying event demultiplexing functions are widely available. 27 | 28 | * The separation of interface and implementation enables easy adaption or extension of the services. 29 | 30 | * Overall structure supports the concurrent execution. 31 | 32 | ## Disadvantages 33 | 34 | * Requires an event demultiplexing system call. 35 | 36 | * A long-running event handler can block the Reactor. 37 | 38 | * The inversion of control makes testing and debugging more difficult. 39 | 40 | ## Examples 41 | * 42 | * 43 | * [Patterns in C – Part 5: REACTOR](https://www.adamtornhill.com/Patterns%20in%20C%205,%20REACTOR.pdf) 44 | * 45 | -------------------------------------------------------------------------------- /ScopedLocking/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ScopedLock) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | scopedLock.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /ScopedLocking/README.md: -------------------------------------------------------------------------------- 1 | # Scoped Locking 2 | 3 | * Dealing with Mutation Pattern 4 | 5 | * Scoped Locking is RAII applied to locking 6 | 7 | ## Idea: 8 | 9 | * Bind the acquiring (constructor) and the releasing (destructor) of 10 | the resource to the lifetime of an object. 11 | * The lifetime of the object is bound. 12 | * The C++ run time is responsible for invoking the destructor and 13 | releasing the resource. 14 | 15 | ## C++ Implementation 16 | * [std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard) and [std::scoped_lock](https://en.cppreference.com/w/cpp/thread/scoped_lock) 17 | * [std::unique_lock](https://en.cppreference.com/w/cpp/thread/unique_lock) and [std::shared_lock](https://en.cppreference.com/w/cpp/thread/shared_lock) 18 | 19 | -------------------------------------------------------------------------------- /ScopedLocking/scopedLock.cpp: -------------------------------------------------------------------------------- 1 | // scopedLock.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class ScopedLock{ 9 | private: 10 | std::mutex& mut; 11 | public: 12 | explicit ScopedLock(std::mutex& m): mut(m){ 13 | mut.lock(); 14 | std::cout << "Lock the mutex: " << &mut << '\n'; 15 | } 16 | ~ScopedLock(){ 17 | std::cout << "Release the mutex: " << &mut << '\n'; 18 | mut.unlock(); 19 | } 20 | }; 21 | 22 | int main(){ 23 | 24 | std::cout << '\n'; 25 | 26 | std::mutex mutex1; 27 | ScopedLock scopedLock1{mutex1}; 28 | 29 | std::cout << "\nBefore local scope" << '\n'; 30 | { 31 | std::mutex mutex2; 32 | ScopedLock scopedLock2{mutex2}; 33 | } 34 | std::cout << "After local scope" << '\n'; 35 | 36 | std::cout << "\nBefore try-catch block" << '\n'; 37 | try{ 38 | std::mutex mutex3; 39 | ScopedLock scopedLock3{mutex3}; 40 | throw std::bad_alloc(); 41 | } 42 | catch (std::bad_alloc& e){ 43 | std::cout << e.what(); 44 | } 45 | std::cout << "\nAfter try-catch block" << '\n'; 46 | 47 | std::cout << '\n'; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /SeqLock/README.md: -------------------------------------------------------------------------------- 1 | # Seqlock 2 | * A special locking mechanism for supporting fast writes of shared variables between two threads. 3 | * It is used to avoid the problem of writer starvation. 4 | * Used for data that is rarely written to. 5 | * Reader never blocks and is willing to retry if that information changes. 6 | 7 | ## Implementation 8 | * Writer increments the sequence number, both after acquiring the lock and before releasing the lock. 9 | * Readers read the sequence number before and after reading the shared data. 10 | * If the sequence number is odd on either occasion, a writer had taken the lock while the data was being read and it may have changed. 11 | * If the sequence numbers are different, a writer has changed the data while it was being read. 12 | n either case readers simply retry until they read the same even sequence number before and after. 13 | 14 | ## Considerations 15 | * Seqlocks are more efficient than traditional read-write locks for the situation where there are many readers and few writers. 16 | * If there is too much write activity or the reader is too slow, they might livelock and the readers may starve. 17 | * Does not work for data that contains pointers, because any writer could invalidate a pointer that a reader has already followed. 18 | * Impossible to step through it with a debugger! 19 | * The sanitizers and [helgrind](https://valgrind.org/docs/manual/hg-manual.html) tool will always suspect a data race due to missing lock! 20 | 21 | ## Fuzz test arbitrary times 22 | | fuzz test Release build | execution time | 23 | | ------------------------------ | -------------- | 24 | | test_seqlock_mutex_emulator | 96.07 sec | 25 | | test_seqlock_emulator | 114.24 sec | 26 | | test_seqlock_posix_emulator | 116.49 sec | 27 | | test_seqlock_naive | 2.45 sec | 28 | | test_seqlock_spinlock | 0.64 sec | 29 | | test_seqlock_rigtorp | 1.16 sec | 30 | 31 | ## Perf data 32 | * Command 33 | ``` 34 | perf stat -e instructions,cpu-cycles,L1-dcache-loads,L1-dcache-load-misses,mem_inst_retired.lock_loads,duration_time 35 | ``` 36 | 37 | | Example Release build | instructions | cpu-cycles | L1-dcache-loads | L1-dcache-load-misses | mem_inst_retired.lock_loads | duration_time | 38 | | ------------------------- | ------------ | ---------- | --------------- | --------------------- | --------------------------- | ------------- | 39 | | seqlock_mutex_emulator | 4152610 | 4832960 | 1057729 | 53110 | 11503 | 1818663 ns | 40 | | seqlock_emulator | 4281531 | 4751058 | 1090099 | 54095 | 13829 | 3070747 ns | 41 | | seqlock_posix_emulator | 4176506 | 4689458 | 1064256 | 53317 | 11170 | 2992380 ns | 42 | | seqlock_naive | 4222576 | 4598630 | 1092859 | 53092 | 9377 | 2904287 ns | 43 | | seqlock_spinlock | 4543389 | 4671161 | 1212866 | 53894 | 9323 | 2933602 ns | 44 | | seqlock_rigtorp | 4394077 | 4769689 | 1147175 | 53246 | 9732 | 2724176 ns | 45 | 46 | ## References 47 | * [Wikipedia Seqlock](https://en.wikipedia.org/wiki/Seqlock) 48 | * [Sequence counters and sequential locks](https://docs.kernel.org/locking/seqlock.html) 49 | * [std::shared_mutex](https://en.cppreference.com/w/cpp/thread/shared_mutex) 50 | * [std::shared_lock](https://en.cppreference.com/w/cpp/thread/shared_lock/lock) 51 | * [Synchronized Output Streams with C++20](https://www.linkedin.com/pulse/synchronized-outputstreams-c20-rainer-grimm) 52 | * [StackOverflow: how to implement a seqlock lock using c++11 atomic library](https://stackoverflow.com/questions/20342691/how-to-implement-a-seqlock-lock-using-c11-atomic-library) 53 | * 54 | * [std::hardware_destructive_interference_size](https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size) 55 | -------------------------------------------------------------------------------- /SeqLock/seqlock_emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_emulator) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | Seqlock.hpp 9 | ) 10 | 11 | set_target_properties( 12 | ${PROJECT_NAME} 13 | PROPERTIES 14 | CXX_STANDARD 20 15 | CXX_STANDARD_REQUIRED YES 16 | CXX_EXTENSIONS NO 17 | ) 18 | 19 | set_target_properties( 20 | ${PROJECT_NAME} 21 | PROPERTIES 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | LINKER_LANGUAGE CXX 26 | ) 27 | 28 | # Example 29 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 30 | 31 | add_executable( 32 | ${EXAMPLE_NAME} 33 | main.cpp 34 | ) 35 | 36 | set_target_properties( 37 | ${EXAMPLE_NAME} 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS NO 42 | ) 43 | 44 | target_compile_options( 45 | ${EXAMPLE_NAME} 46 | PRIVATE 47 | $<$:/W4 /WX> 48 | $<$>:-Wall -Wextra -Wpedantic> 49 | ) 50 | 51 | target_link_libraries( 52 | ${EXAMPLE_NAME} 53 | PRIVATE 54 | ${PROJECT_NAME} 55 | ) 56 | 57 | # Test 58 | include(CTest) 59 | 60 | set(TEST_NAME test_${PROJECT_NAME}) 61 | 62 | add_executable( 63 | ${TEST_NAME} 64 | test.cpp 65 | ) 66 | 67 | set_target_properties( 68 | ${TEST_NAME} 69 | PROPERTIES 70 | CXX_STANDARD 20 71 | CXX_STANDARD_REQUIRED YES 72 | CXX_EXTENSIONS NO 73 | ) 74 | 75 | target_compile_options( 76 | ${TEST_NAME} 77 | PRIVATE 78 | $<$:/W4 /WX> 79 | $<$>:-Wall -Wextra -Wpedantic> 80 | ) 81 | 82 | target_link_libraries( 83 | ${TEST_NAME} 84 | PRIVATE 85 | ${PROJECT_NAME} 86 | ) 87 | 88 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 89 | -------------------------------------------------------------------------------- /SeqLock/seqlock_emulator/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | requires std::is_nothrow_copy_assignable_v && 8 | std::is_trivially_copy_assignable_v 9 | class Seqlock { 10 | public: 11 | T load() const noexcept { 12 | T copy; 13 | { 14 | std::shared_lock lock(mSharedMutex); 15 | copy = mSharedValue; 16 | } 17 | return copy; 18 | } 19 | 20 | void store(const T &aValue) noexcept { 21 | std::lock_guard file_lock(mSharedMutex); 22 | mSharedValue = aValue; 23 | } 24 | 25 | private: 26 | mutable std::shared_mutex mSharedMutex; 27 | T mSharedValue{}; 28 | }; 29 | -------------------------------------------------------------------------------- /SeqLock/seqlock_emulator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Seqlock.hpp" 6 | 7 | struct Data { 8 | std::size_t a, b, c; 9 | }; 10 | 11 | std::atomic_bool exitFlag = false; 12 | 13 | std::hash hasher; 14 | 15 | template 16 | std::string reader(Seqlock& aSeqlock) { 17 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 18 | for (; !exitFlag;) { 19 | auto readData = aSeqlock.load(); 20 | if (readData.a + 100 == readData.b && 21 | readData.c == readData.a + readData.b) { 22 | return result + " success"; 23 | } 24 | } 25 | return result + " fail"; 26 | } 27 | 28 | template 29 | void writer(Seqlock& aSeqlock) { 30 | aSeqlock.store({3, 2, 1}); 31 | aSeqlock.store({100, 200, 300}); 32 | } 33 | 34 | int main() { 35 | Seqlock seqlock; 36 | 37 | auto firstReader = 38 | std::async(std::launch::async, reader, std::ref(seqlock)); 39 | auto secondReader = 40 | std::async(std::launch::async, reader, std::ref(seqlock)); 41 | 42 | std::thread writerThread(writer, std::ref(seqlock)); 43 | 44 | writerThread.join(); 45 | exitFlag = true; 46 | 47 | std::cout << firstReader.get() << '\n'; 48 | std::cout << secondReader.get() << '\n'; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /SeqLock/seqlock_emulator/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Seqlock.hpp" 9 | 10 | 11 | constexpr std::size_t kOffset = 100; 12 | constexpr int kThreadCount = 100; 13 | constexpr std::size_t kOperationsCount = 10000000; 14 | 15 | // Fuzz test 16 | int main() { 17 | struct Data { 18 | std::size_t a, b, c; 19 | }; 20 | 21 | Seqlock seqLock; 22 | std::atomic isReady{0}; 23 | std::vector threads; 24 | 25 | for (int i = 0; i < kThreadCount; ++i) { 26 | threads.push_back(std::thread([&seqLock, &isReady]() { 27 | while (isReady == 0) { 28 | } 29 | 30 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 31 | auto copy = seqLock.load(); 32 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 33 | exit(EXIT_FAILURE); 34 | } 35 | } 36 | 37 | isReady--; 38 | })); 39 | } 40 | 41 | std::size_t counter = 0; 42 | while (true) { 43 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 44 | seqLock.store(data); 45 | 46 | if (counter == 1) { 47 | isReady += threads.size(); 48 | } 49 | 50 | if (isReady == 0) { 51 | break; 52 | } 53 | } 54 | 55 | for (auto &t : threads) { 56 | t.join(); 57 | } 58 | 59 | return EXIT_SUCCESS; 60 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_mutex_emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_mutex_emulator) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | Seqlock.hpp 9 | ) 10 | 11 | set_target_properties( 12 | ${PROJECT_NAME} 13 | PROPERTIES 14 | CXX_STANDARD 20 15 | CXX_STANDARD_REQUIRED YES 16 | CXX_EXTENSIONS NO 17 | ) 18 | 19 | set_target_properties( 20 | ${PROJECT_NAME} 21 | PROPERTIES 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | LINKER_LANGUAGE CXX 26 | ) 27 | 28 | # Example 29 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 30 | 31 | add_executable( 32 | ${EXAMPLE_NAME} 33 | main.cpp 34 | ) 35 | 36 | set_target_properties( 37 | ${EXAMPLE_NAME} 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS NO 42 | ) 43 | 44 | target_compile_options( 45 | ${EXAMPLE_NAME} 46 | PRIVATE 47 | $<$:/W4 /WX> 48 | $<$>:-Wall -Wextra -Wpedantic> 49 | ) 50 | 51 | target_link_libraries( 52 | ${EXAMPLE_NAME} 53 | PRIVATE 54 | ${PROJECT_NAME} 55 | ) 56 | 57 | # Test 58 | include(CTest) 59 | 60 | set(TEST_NAME test_${PROJECT_NAME}) 61 | 62 | add_executable( 63 | ${TEST_NAME} 64 | test.cpp 65 | ) 66 | 67 | set_target_properties( 68 | ${TEST_NAME} 69 | PROPERTIES 70 | CXX_STANDARD 20 71 | CXX_STANDARD_REQUIRED YES 72 | CXX_EXTENSIONS NO 73 | ) 74 | 75 | target_compile_options( 76 | ${TEST_NAME} 77 | PRIVATE 78 | $<$:/W4 /WX> 79 | $<$>:-Wall -Wextra -Wpedantic> 80 | ) 81 | 82 | target_link_libraries( 83 | ${TEST_NAME} 84 | PRIVATE 85 | ${PROJECT_NAME} 86 | ) 87 | 88 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 89 | -------------------------------------------------------------------------------- /SeqLock/seqlock_mutex_emulator/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | requires std::is_nothrow_copy_assignable_v && 8 | std::is_trivially_copy_assignable_v 9 | class Seqlock { 10 | public: 11 | T load() const noexcept { 12 | T copy; 13 | { 14 | std::lock_guard lock(mMutex); 15 | copy = mSharedValue; 16 | } 17 | return copy; 18 | } 19 | 20 | void store(const T &aValue) noexcept { 21 | std::lock_guard file_lock(mMutex); 22 | mSharedValue = aValue; 23 | } 24 | 25 | private: 26 | mutable std::mutex mMutex; 27 | T mSharedValue{}; 28 | }; 29 | -------------------------------------------------------------------------------- /SeqLock/seqlock_mutex_emulator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Seqlock.hpp" 6 | 7 | struct Data { 8 | std::size_t a, b, c; 9 | }; 10 | 11 | std::atomic_bool exitFlag = false; 12 | 13 | std::hash hasher; 14 | 15 | template 16 | std::string reader(Seqlock& aSeqlock) { 17 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 18 | for (; !exitFlag;) { 19 | auto readData = aSeqlock.load(); 20 | if (readData.a + 100 == readData.b && 21 | readData.c == readData.a + readData.b) { 22 | return result + " success"; 23 | } 24 | } 25 | return result + " fail"; 26 | } 27 | 28 | template 29 | void writer(Seqlock& aSeqlock) { 30 | aSeqlock.store({3, 2, 1}); 31 | aSeqlock.store({100, 200, 300}); 32 | } 33 | 34 | int main() { 35 | Seqlock seqlock; 36 | 37 | auto firstReader = 38 | std::async(std::launch::async, reader, std::ref(seqlock)); 39 | auto secondReader = 40 | std::async(std::launch::async, reader, std::ref(seqlock)); 41 | 42 | std::thread writerThread(writer, std::ref(seqlock)); 43 | 44 | writerThread.join(); 45 | exitFlag = true; 46 | 47 | std::cout << firstReader.get() << '\n'; 48 | std::cout << secondReader.get() << '\n'; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /SeqLock/seqlock_mutex_emulator/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Seqlock.hpp" 9 | 10 | constexpr std::size_t kOffset = 100; 11 | constexpr int kThreadCount = 100; 12 | constexpr std::size_t kOperationsCount = 10000000; 13 | 14 | // Fuzz test 15 | int main() { 16 | struct Data { 17 | std::size_t a, b, c; 18 | }; 19 | 20 | Seqlock seqLock; 21 | std::atomic isReady{0}; 22 | std::vector threads; 23 | 24 | for (int i = 0; i < kThreadCount; ++i) { 25 | threads.push_back(std::thread([&seqLock, &isReady]() { 26 | while (isReady == 0) { 27 | } 28 | 29 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 30 | auto copy = seqLock.load(); 31 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 32 | exit(EXIT_FAILURE); 33 | } 34 | } 35 | 36 | isReady--; 37 | })); 38 | } 39 | 40 | std::size_t counter = 0; 41 | while (true) { 42 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 43 | seqLock.store(data); 44 | 45 | if (counter == 1) { 46 | isReady += threads.size(); 47 | } 48 | 49 | if (isReady == 0) { 50 | break; 51 | } 52 | } 53 | 54 | for (auto &t : threads) { 55 | t.join(); 56 | } 57 | 58 | return EXIT_SUCCESS; 59 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_naive/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_naive) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | Seqlock.hpp 9 | ) 10 | 11 | set_target_properties( 12 | ${PROJECT_NAME} 13 | PROPERTIES 14 | CXX_STANDARD 20 15 | CXX_STANDARD_REQUIRED YES 16 | CXX_EXTENSIONS NO 17 | ) 18 | 19 | set_target_properties( 20 | ${PROJECT_NAME} 21 | PROPERTIES 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | LINKER_LANGUAGE CXX 26 | ) 27 | 28 | # Example 29 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 30 | 31 | add_executable( 32 | ${EXAMPLE_NAME} 33 | main.cpp 34 | ) 35 | 36 | set_target_properties( 37 | ${EXAMPLE_NAME} 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS NO 42 | ) 43 | 44 | target_compile_options( 45 | ${EXAMPLE_NAME} 46 | PRIVATE 47 | $<$:/W4 /WX> 48 | $<$>:-Wall -Wextra -Wpedantic> 49 | ) 50 | 51 | target_link_libraries( 52 | ${EXAMPLE_NAME} 53 | PRIVATE 54 | ${PROJECT_NAME} 55 | ) 56 | 57 | # Test 58 | include(CTest) 59 | 60 | set(TEST_NAME test_${PROJECT_NAME}) 61 | 62 | add_executable( 63 | ${TEST_NAME} 64 | test.cpp 65 | ) 66 | 67 | set_target_properties( 68 | ${TEST_NAME} 69 | PROPERTIES 70 | CXX_STANDARD 20 71 | CXX_STANDARD_REQUIRED YES 72 | CXX_EXTENSIONS NO 73 | ) 74 | 75 | target_compile_options( 76 | ${TEST_NAME} 77 | PRIVATE 78 | $<$:/W4 /WX> 79 | $<$>:-Wall -Wextra -Wpedantic> 80 | ) 81 | 82 | target_link_libraries( 83 | ${TEST_NAME} 84 | PRIVATE 85 | ${PROJECT_NAME} 86 | ) 87 | 88 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 89 | -------------------------------------------------------------------------------- /SeqLock/seqlock_naive/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | requires std::is_nothrow_copy_assignable_v && 8 | std::is_trivially_copy_assignable_v 9 | class Seqlock { 10 | public: 11 | T load() const noexcept { 12 | T copy; 13 | for (;;) { 14 | const auto startFlag = mCounter.load(); 15 | if ((startFlag & 1) == 1) { 16 | continue; 17 | } 18 | copy = mSharedValue; 19 | const auto endFlag = mCounter.load(); 20 | if (startFlag == endFlag) { 21 | break; 22 | } 23 | } 24 | return copy; 25 | } 26 | 27 | void store(const T &aValue) noexcept { 28 | mCounter++; 29 | mSharedValue = aValue; 30 | mCounter++; 31 | } 32 | 33 | private: 34 | std::atomic_uint32_t mCounter{0}; 35 | T mSharedValue{}; 36 | }; 37 | -------------------------------------------------------------------------------- /SeqLock/seqlock_naive/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Seqlock.hpp" 6 | 7 | struct Data { 8 | std::size_t a, b, c; 9 | }; 10 | 11 | std::atomic_bool exitFlag = false; 12 | 13 | std::hash hasher; 14 | 15 | template 16 | std::string reader(Seqlock& aSeqlock) { 17 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 18 | for (; !exitFlag;) { 19 | auto readData = aSeqlock.load(); 20 | if (readData.a + 100 == readData.b && 21 | readData.c == readData.a + readData.b) { 22 | return result + " success"; 23 | } 24 | } 25 | return result + " fail"; 26 | } 27 | 28 | template 29 | void writer(Seqlock& aSeqlock) { 30 | aSeqlock.store({3, 2, 1}); 31 | aSeqlock.store({100, 200, 300}); 32 | } 33 | 34 | int main() { 35 | Seqlock seqlock; 36 | 37 | auto firstReader = 38 | std::async(std::launch::async, reader, std::ref(seqlock)); 39 | auto secondReader = 40 | std::async(std::launch::async, reader, std::ref(seqlock)); 41 | 42 | std::thread writerThread(writer, std::ref(seqlock)); 43 | 44 | writerThread.join(); 45 | exitFlag = true; 46 | 47 | std::cout << firstReader.get() << '\n'; 48 | std::cout << secondReader.get() << '\n'; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /SeqLock/seqlock_naive/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Seqlock.hpp" 9 | 10 | constexpr std::size_t kOffset = 100; 11 | constexpr int kThreadCount = 100; 12 | constexpr std::size_t kOperationsCount = 10000000; 13 | 14 | // Fuzz test 15 | int main() { 16 | struct Data { 17 | std::size_t a, b, c; 18 | }; 19 | 20 | Seqlock seqLock; 21 | std::atomic isReady{0}; 22 | std::vector threads; 23 | 24 | for (int i = 0; i < kThreadCount; ++i) { 25 | threads.push_back(std::thread([&seqLock, &isReady]() { 26 | while (isReady == 0) { 27 | } 28 | 29 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 30 | auto copy = seqLock.load(); 31 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 32 | exit(EXIT_FAILURE); 33 | } 34 | } 35 | 36 | isReady--; 37 | })); 38 | } 39 | 40 | std::size_t counter = 0; 41 | while (true) { 42 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 43 | seqLock.store(data); 44 | 45 | if (counter == 1) { 46 | isReady += threads.size(); 47 | } 48 | 49 | if (isReady == 0) { 50 | break; 51 | } 52 | } 53 | 54 | for (auto &t : threads) { 55 | t.join(); 56 | } 57 | 58 | return EXIT_SUCCESS; 59 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_posix_emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_posix_emulator) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | Seqlock.hpp 9 | ) 10 | 11 | set_target_properties( 12 | ${PROJECT_NAME} 13 | PROPERTIES 14 | CXX_STANDARD 20 15 | CXX_STANDARD_REQUIRED YES 16 | CXX_EXTENSIONS NO 17 | ) 18 | 19 | set_target_properties( 20 | ${PROJECT_NAME} 21 | PROPERTIES 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | LINKER_LANGUAGE CXX 26 | ) 27 | 28 | # Example 29 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 30 | 31 | add_executable( 32 | ${EXAMPLE_NAME} 33 | main.cpp 34 | ) 35 | 36 | set_target_properties( 37 | ${EXAMPLE_NAME} 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS NO 42 | ) 43 | 44 | target_compile_options( 45 | ${EXAMPLE_NAME} 46 | PRIVATE 47 | $<$:/W4 /WX> 48 | $<$>:-Wall -Wextra -Wpedantic> 49 | ) 50 | 51 | target_link_libraries( 52 | ${EXAMPLE_NAME} 53 | PRIVATE 54 | ${PROJECT_NAME} 55 | ) 56 | 57 | # Test 58 | include(CTest) 59 | 60 | set(TEST_NAME test_${PROJECT_NAME}) 61 | 62 | add_executable( 63 | ${TEST_NAME} 64 | test.cpp 65 | ) 66 | 67 | set_target_properties( 68 | ${TEST_NAME} 69 | PROPERTIES 70 | CXX_STANDARD 20 71 | CXX_STANDARD_REQUIRED YES 72 | CXX_EXTENSIONS NO 73 | ) 74 | 75 | target_compile_options( 76 | ${TEST_NAME} 77 | PRIVATE 78 | $<$:/W4 /WX> 79 | $<$>:-Wall -Wextra -Wpedantic> 80 | ) 81 | 82 | target_link_libraries( 83 | ${TEST_NAME} 84 | PRIVATE 85 | ${PROJECT_NAME} 86 | ) 87 | 88 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 89 | -------------------------------------------------------------------------------- /SeqLock/seqlock_posix_emulator/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | template 8 | requires std::is_nothrow_copy_assignable_v && 9 | std::is_trivially_copy_assignable_v 10 | class Seqlock { 11 | public: 12 | Seqlock() noexcept { pthread_rwlock_init(&mSharedMutex, nullptr); } 13 | 14 | ~Seqlock() noexcept { pthread_rwlock_destroy(&mSharedMutex); } 15 | 16 | T load() const noexcept { 17 | T copy; 18 | { 19 | pthread_rwlock_rdlock(&mSharedMutex); 20 | copy = mSharedValue; 21 | pthread_rwlock_unlock(&mSharedMutex); 22 | } 23 | return copy; 24 | } 25 | 26 | void store(const T &aValue) noexcept { 27 | pthread_rwlock_wrlock(&mSharedMutex); 28 | mSharedValue = aValue; 29 | pthread_rwlock_unlock(&mSharedMutex); 30 | } 31 | 32 | private: 33 | mutable pthread_rwlock_t mSharedMutex; 34 | T mSharedValue{}; 35 | }; 36 | -------------------------------------------------------------------------------- /SeqLock/seqlock_posix_emulator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Seqlock.hpp" 6 | 7 | struct Data { 8 | std::size_t a, b, c; 9 | }; 10 | 11 | std::atomic_bool exitFlag = false; 12 | 13 | std::hash hasher; 14 | 15 | template 16 | std::string reader(Seqlock& aSeqlock) { 17 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 18 | for (; !exitFlag;) { 19 | auto readData = aSeqlock.load(); 20 | if (readData.a + 100 == readData.b && 21 | readData.c == readData.a + readData.b) { 22 | return result + " success"; 23 | } 24 | } 25 | return result + " fail"; 26 | } 27 | 28 | template 29 | void writer(Seqlock& aSeqlock) { 30 | aSeqlock.store({3, 2, 1}); 31 | aSeqlock.store({100, 200, 300}); 32 | } 33 | 34 | int main() { 35 | Seqlock seqlock; 36 | 37 | auto firstReader = 38 | std::async(std::launch::async, reader, std::ref(seqlock)); 39 | auto secondReader = 40 | std::async(std::launch::async, reader, std::ref(seqlock)); 41 | 42 | std::thread writerThread(writer, std::ref(seqlock)); 43 | 44 | writerThread.join(); 45 | exitFlag = true; 46 | 47 | std::cout << firstReader.get() << '\n'; 48 | std::cout << secondReader.get() << '\n'; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /SeqLock/seqlock_posix_emulator/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Seqlock.hpp" 9 | 10 | 11 | constexpr std::size_t kOffset = 100; 12 | constexpr int kThreadCount = 100; 13 | constexpr std::size_t kOperationsCount = 10000000; 14 | 15 | // Fuzz test 16 | int main() { 17 | struct Data { 18 | std::size_t a, b, c; 19 | }; 20 | 21 | Seqlock seqLock; 22 | std::atomic isReady{0}; 23 | std::vector threads; 24 | 25 | for (int i = 0; i < kThreadCount; ++i) { 26 | threads.push_back(std::thread([&seqLock, &isReady]() { 27 | while (isReady == 0) { 28 | } 29 | 30 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 31 | auto copy = seqLock.load(); 32 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 33 | exit(EXIT_FAILURE); 34 | } 35 | } 36 | 37 | isReady--; 38 | })); 39 | } 40 | 41 | std::size_t counter = 0; 42 | while (true) { 43 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 44 | seqLock.store(data); 45 | 46 | if (counter == 1) { 47 | isReady += threads.size(); 48 | } 49 | 50 | if (isReady == 0) { 51 | break; 52 | } 53 | } 54 | 55 | for (auto &t : threads) { 56 | t.join(); 57 | } 58 | 59 | return EXIT_SUCCESS; 60 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_rigtorp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_rigtorp) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | rigtorp/Seqlock.hpp 9 | ) 10 | 11 | set_target_properties( 12 | ${PROJECT_NAME} 13 | PROPERTIES 14 | CXX_STANDARD 20 15 | CXX_STANDARD_REQUIRED YES 16 | CXX_EXTENSIONS NO 17 | ) 18 | 19 | set_target_properties( 20 | ${PROJECT_NAME} 21 | PROPERTIES 22 | CXX_STANDARD 20 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_EXTENSIONS NO 25 | LINKER_LANGUAGE CXX 26 | ) 27 | 28 | # Example 29 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 30 | 31 | add_executable( 32 | ${EXAMPLE_NAME} 33 | main.cpp 34 | ) 35 | 36 | set_target_properties( 37 | ${EXAMPLE_NAME} 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS NO 42 | ) 43 | 44 | target_compile_options( 45 | ${EXAMPLE_NAME} 46 | PRIVATE 47 | $<$:/W4 /WX> 48 | $<$>:-Wall -Wextra -Wpedantic> 49 | ) 50 | 51 | target_link_libraries( 52 | ${EXAMPLE_NAME} 53 | PRIVATE 54 | ${PROJECT_NAME} 55 | ) 56 | 57 | # Test 58 | include(CTest) 59 | 60 | set(TEST_NAME test_${PROJECT_NAME}) 61 | 62 | add_executable( 63 | ${TEST_NAME} 64 | test.cpp 65 | ) 66 | 67 | set_target_properties( 68 | ${TEST_NAME} 69 | PROPERTIES 70 | CXX_STANDARD 20 71 | CXX_STANDARD_REQUIRED YES 72 | CXX_EXTENSIONS NO 73 | ) 74 | 75 | target_compile_options( 76 | ${TEST_NAME} 77 | PRIVATE 78 | $<$:/W4 /WX> 79 | $<$>:-Wall -Wextra -Wpedantic> 80 | ) 81 | 82 | target_link_libraries( 83 | ${TEST_NAME} 84 | PRIVATE 85 | ${PROJECT_NAME} 86 | ) 87 | 88 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 89 | -------------------------------------------------------------------------------- /SeqLock/seqlock_rigtorp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // https://github.com/rigtorp/Seqlock/ 6 | #include "rigtorp/Seqlock.hpp" 7 | 8 | using rigtorp::Seqlock; 9 | 10 | struct Data { 11 | std::size_t a, b, c; 12 | }; 13 | 14 | std::atomic_bool exitFlag = false; 15 | 16 | std::hash hasher; 17 | 18 | template 19 | std::string reader(Seqlock& aSeqlock) { 20 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 21 | for (; !exitFlag;) { 22 | auto readData = aSeqlock.load(); 23 | if (readData.a + 100 == readData.b && 24 | readData.c == readData.a + readData.b) { 25 | return result + " success"; 26 | } 27 | } 28 | return result + " fail"; 29 | } 30 | 31 | template 32 | void writer(Seqlock& aSeqlock) { 33 | aSeqlock.store({3, 2, 1}); 34 | aSeqlock.store({100, 200, 300}); 35 | } 36 | 37 | int main() { 38 | Seqlock seqlock; 39 | 40 | auto firstReader = 41 | std::async(std::launch::async, reader, std::ref(seqlock)); 42 | auto secondReader = 43 | std::async(std::launch::async, reader, std::ref(seqlock)); 44 | 45 | std::thread writerThread(writer, std::ref(seqlock)); 46 | 47 | writerThread.join(); 48 | exitFlag = true; 49 | 50 | std::cout << firstReader.get() << '\n'; 51 | std::cout << secondReader.get() << '\n'; 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /SeqLock/seqlock_rigtorp/rigtorp/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Erik Rigtorp 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | #ifndef NDEBUG 29 | #define RIGTORP_SEQLOCK_NOINLINE __attribute__((noinline)) 30 | #else 31 | #define RIGTORP_SEQLOCK_NOINLINE 32 | #endif 33 | 34 | namespace rigtorp { 35 | 36 | // Updates: 37 | // * concepts 38 | // * hardware_destructive_interference_size 39 | // * initialization of value_ 40 | template 41 | requires std::is_nothrow_copy_assignable_v && 42 | std::is_trivially_copy_assignable_v 43 | class 44 | // alignas(std::hardware_constructive_interference_size) 45 | Seqlock { 46 | public: 47 | static_assert(std::is_nothrow_copy_assignable::value, 48 | "T must satisfy is_nothrow_copy_assignable"); 49 | static_assert(std::is_trivially_copy_assignable::value, 50 | "T must satisfy is_trivially_copy_assignable"); 51 | 52 | Seqlock() : seq_(0) {} 53 | 54 | RIGTORP_SEQLOCK_NOINLINE T load() const noexcept { 55 | T copy; 56 | std::size_t seq0, seq1; 57 | do { 58 | seq0 = seq_.load(std::memory_order_acquire); 59 | std::atomic_signal_fence(std::memory_order_acq_rel); 60 | copy = value_; 61 | std::atomic_signal_fence(std::memory_order_acq_rel); 62 | seq1 = seq_.load(std::memory_order_acquire); 63 | } while (seq0 != seq1 || seq0 & 1); 64 | return copy; 65 | } 66 | 67 | RIGTORP_SEQLOCK_NOINLINE void store(const T &desired) noexcept { 68 | std::size_t seq0 = seq_.load(std::memory_order_relaxed); 69 | seq_.store(seq0 + 1, std::memory_order_release); 70 | std::atomic_signal_fence(std::memory_order_acq_rel); 71 | value_ = desired; 72 | std::atomic_signal_fence(std::memory_order_acq_rel); 73 | seq_.store(seq0 + 2, std::memory_order_release); 74 | } 75 | 76 | private: 77 | // std::hardware_destructive_interference_size 78 | static const std::size_t kFalseSharingRange = 128; 79 | 80 | // Align to prevent false sharing with adjecent data 81 | alignas(kFalseSharingRange) T value_{}; 82 | std::atomic seq_; 83 | // Padding to prevent false sharing with adjecent data 84 | // Next lines not needed when "class 85 | // alignas(std::hardware_constructive_interference_size)" 86 | char padding_[kFalseSharingRange - 87 | ((sizeof(value_) + sizeof(seq_)) % kFalseSharingRange)]; 88 | static_assert( 89 | ((sizeof(value_) + sizeof(seq_) + sizeof(padding_)) % 90 | kFalseSharingRange) == 0, 91 | "sizeof(Seqlock) should be a multiple of kFalseSharingRange"); 92 | }; 93 | } // namespace rigtorp 94 | -------------------------------------------------------------------------------- /SeqLock/seqlock_rigtorp/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rigtorp/Seqlock.hpp" 9 | 10 | using rigtorp::Seqlock; 11 | 12 | constexpr std::size_t kOffset = 100; 13 | constexpr int kThreadCount = 100; 14 | constexpr std::size_t kOperationsCount = 10000000; 15 | 16 | // Fuzz test 17 | int main() { 18 | struct Data { 19 | std::size_t a, b, c; 20 | }; 21 | 22 | Seqlock seqLock; 23 | std::atomic isReady{0}; 24 | std::vector threads; 25 | 26 | for (int i = 0; i < kThreadCount; ++i) { 27 | threads.push_back(std::thread([&seqLock, &isReady]() { 28 | while (isReady == 0) { 29 | } 30 | 31 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 32 | auto copy = seqLock.load(); 33 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 34 | exit(EXIT_FAILURE); 35 | } 36 | } 37 | 38 | isReady--; 39 | })); 40 | } 41 | 42 | std::size_t counter = 0; 43 | while (true) { 44 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 45 | seqLock.store(data); 46 | 47 | if (counter == 1) { 48 | isReady += threads.size(); 49 | } 50 | 51 | if (isReady == 0) { 52 | break; 53 | } 54 | } 55 | 56 | for (auto &t : threads) { 57 | t.join(); 58 | } 59 | 60 | return EXIT_SUCCESS; 61 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(seqlock_spinlock) 4 | 5 | # Library 6 | add_library( 7 | ${PROJECT_NAME} 8 | SpinLock.hpp 9 | SpinLock.cpp 10 | Seqlock.hpp 11 | ) 12 | 13 | set_target_properties( 14 | ${PROJECT_NAME} 15 | PROPERTIES 16 | CXX_STANDARD 20 17 | CXX_STANDARD_REQUIRED YES 18 | CXX_EXTENSIONS NO 19 | ) 20 | 21 | set_target_properties( 22 | ${PROJECT_NAME} 23 | PROPERTIES 24 | CXX_STANDARD 20 25 | CXX_STANDARD_REQUIRED YES 26 | CXX_EXTENSIONS NO 27 | LINKER_LANGUAGE CXX 28 | ) 29 | 30 | # Example 31 | set(EXAMPLE_NAME example_${PROJECT_NAME}) 32 | 33 | add_executable( 34 | ${EXAMPLE_NAME} 35 | main.cpp 36 | ) 37 | 38 | set_target_properties( 39 | ${EXAMPLE_NAME} 40 | PROPERTIES 41 | CXX_STANDARD 20 42 | CXX_STANDARD_REQUIRED YES 43 | CXX_EXTENSIONS NO 44 | ) 45 | 46 | target_compile_options( 47 | ${EXAMPLE_NAME} 48 | PRIVATE 49 | $<$:/W4 /WX> 50 | $<$>:-Wall -Wextra -Wpedantic> 51 | ) 52 | 53 | target_link_libraries( 54 | ${EXAMPLE_NAME} 55 | PRIVATE 56 | ${PROJECT_NAME} 57 | ) 58 | 59 | # Test 60 | include(CTest) 61 | 62 | set(TEST_NAME test_${PROJECT_NAME}) 63 | 64 | add_executable( 65 | ${TEST_NAME} 66 | test.cpp 67 | ) 68 | 69 | set_target_properties( 70 | ${TEST_NAME} 71 | PROPERTIES 72 | CXX_STANDARD 20 73 | CXX_STANDARD_REQUIRED YES 74 | CXX_EXTENSIONS NO 75 | ) 76 | 77 | target_compile_options( 78 | ${TEST_NAME} 79 | PRIVATE 80 | $<$:/W4 /WX> 81 | $<$>:-Wall -Wextra -Wpedantic> 82 | ) 83 | 84 | target_link_libraries( 85 | ${TEST_NAME} 86 | PRIVATE 87 | ${PROJECT_NAME} 88 | ) 89 | 90 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 91 | -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/Seqlock.hpp: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/20342691/how-to-implement-a-seqlock-lock-using-c11-atomic-library 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | #include "SpinLock.hpp" 8 | 9 | template 10 | requires std::is_nothrow_copy_assignable_v && 11 | std::is_trivially_copy_assignable_v 12 | class Seqlock { 13 | public: 14 | T load() const noexcept { 15 | T copy; 16 | uintptr_t flag; 17 | do { 18 | read_enter(flag); 19 | copy = mSharedValue; 20 | } while (!read_leave(flag)); 21 | return copy; 22 | } 23 | 24 | void store(const T& aValue) noexcept { 25 | write_lock(); 26 | mSharedValue = aValue; 27 | write_unlock(); 28 | } 29 | 30 | private: 31 | void write_lock() noexcept { 32 | mLock.lock(); 33 | mFlags.fetch_add(1, std::memory_order_acquire); 34 | } 35 | 36 | void write_unlock() noexcept { 37 | mFlags.fetch_add(1, std::memory_order_release); 38 | mLock.unlock(); 39 | } 40 | 41 | void read_enter(uintptr_t& flag) const noexcept { 42 | for (;;) { 43 | const auto f = mFlags.load(std::memory_order_acquire); 44 | if ((f & 1) == 0) { 45 | flag = f; 46 | break; 47 | } 48 | } 49 | } 50 | 51 | bool read_leave(uintptr_t flag) const noexcept { 52 | atomic_thread_fence(std::memory_order_acquire); 53 | const auto f = mFlags.load(std::memory_order_relaxed); 54 | return f == flag; 55 | } 56 | 57 | std::atomic_uintptr_t mFlags; 58 | SpinLock mLock; 59 | T mSharedValue{}; 60 | }; 61 | -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/SpinLock.cpp: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/20342691/how-to-implement-a-seqlock-lock-using-c11-atomic-library 2 | #include "SpinLock.hpp" 3 | 4 | void SpinLock::lock() noexcept { 5 | while (mFlag.test_and_set(std::memory_order_acquire)) { 6 | mFlag.wait(true, std::memory_order_relaxed); 7 | } 8 | } 9 | 10 | void SpinLock::unlock() noexcept { 11 | mFlag.clear(std::memory_order_release); 12 | mFlag.notify_one(); 13 | } -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/SpinLock.hpp: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/20342691/how-to-implement-a-seqlock-lock-using-c11-atomic-library 2 | #pragma once 3 | 4 | #include 5 | 6 | class SpinLock { 7 | public: 8 | void lock() noexcept; 9 | void unlock() noexcept; 10 | 11 | private: 12 | std::atomic_flag mFlag = ATOMIC_FLAG_INIT; 13 | }; -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Seqlock.hpp" 6 | 7 | struct Data { 8 | std::size_t a, b, c; 9 | }; 10 | 11 | std::atomic_bool exitFlag = false; 12 | 13 | std::hash hasher; 14 | 15 | template 16 | std::string reader(Seqlock& aSeqlock) { 17 | std::string result = std::to_string(hasher(std::this_thread::get_id())); 18 | for (; !exitFlag;) { 19 | auto readData = aSeqlock.load(); 20 | if (readData.a + 100 == readData.b && 21 | readData.c == readData.a + readData.b) { 22 | return result + " success"; 23 | } 24 | } 25 | return result + " fail"; 26 | } 27 | 28 | template 29 | void writer(Seqlock& aSeqlock) { 30 | aSeqlock.store({3, 2, 1}); 31 | aSeqlock.store({100, 200, 300}); 32 | } 33 | 34 | int main() { 35 | Seqlock seqlock; 36 | 37 | auto firstReader = 38 | std::async(std::launch::async, reader, std::ref(seqlock)); 39 | auto secondReader = 40 | std::async(std::launch::async, reader, std::ref(seqlock)); 41 | 42 | std::thread writerThread(writer, std::ref(seqlock)); 43 | 44 | writerThread.join(); 45 | exitFlag = true; 46 | 47 | std::cout << firstReader.get() << '\n'; 48 | std::cout << secondReader.get() << '\n'; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /SeqLock/seqlock_spinlock/test.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/rigtorp/Seqlock/ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Seqlock.hpp" 9 | 10 | 11 | constexpr std::size_t kOffset = 100; 12 | constexpr int kThreadCount = 100; 13 | constexpr std::size_t kOperationsCount = 10000000; 14 | 15 | // Fuzz test 16 | int main() { 17 | struct Data { 18 | std::size_t a, b, c; 19 | }; 20 | 21 | Seqlock seqLock; 22 | std::atomic isReady{0}; 23 | std::vector threads; 24 | 25 | for (int i = 0; i < kThreadCount; ++i) { 26 | threads.push_back(std::thread([&seqLock, &isReady]() { 27 | while (isReady == 0) { 28 | } 29 | 30 | for (std::size_t i = 0; i < kOperationsCount; ++i) { 31 | auto copy = seqLock.load(); 32 | if (copy.a + kOffset != copy.b || copy.c != copy.a + copy.b) { 33 | exit(EXIT_FAILURE); 34 | } 35 | } 36 | 37 | isReady--; 38 | })); 39 | } 40 | 41 | std::size_t counter = 0; 42 | while (true) { 43 | Data data = {counter++, data.a + kOffset, data.b + data.a}; 44 | seqLock.store(data); 45 | 46 | if (counter == 1) { 47 | isReady += threads.size(); 48 | } 49 | 50 | if (isReady == 0) { 51 | break; 52 | } 53 | } 54 | 55 | for (auto &t : threads) { 56 | t.join(); 57 | } 58 | 59 | return EXIT_SUCCESS; 60 | } -------------------------------------------------------------------------------- /Spinlock/README.md: -------------------------------------------------------------------------------- 1 | # Spinlock 2 | * A lock that causes a thread trying to acquire it to simply wait in a loop ("spin") while repeatedly checking whether the lock is available. 3 | 4 | ## Considerations 5 | * Efficient if threads are likely to be blocked for only short periods. 6 | * Wasteful if held for longer durations 7 | * Take into account the possibility of simultaneous access to the lock, which could cause race conditions. 8 | * Implementation is possible with atomic test-and-set operations. 9 | * Consider using exponential back-off to reduce load during wait. 10 | * Measure! 11 | * Don’t `std::thread::yield()` in a loop. 12 | * Don’t sleep 13 | 14 | ## Thread problems check 15 | * GCC sanitizer 16 | ``` 17 | g++ main.cpp -fsanitize=thread -std=c++20 -o main.x 18 | ``` 19 | 20 | * Clang sanitizer (recommended) 21 | ``` 22 | clang++ main.cpp -fsanitize=thread -std=c++20 -o main.x 23 | ``` 24 | 25 | ``` 26 | cmake .. -DUSE_TSAN=ON -DCMAKE_C_COMPILER=/usr/bin/clang 27 | ``` 28 | 29 | ``` 30 | cmake .. -DUSE_TSAN=ON -DCMAKE_CXX_COMPILER=/usr/bin/clang++ 31 | ``` 32 | 33 | 34 | * Valgrind [Helgrind](https://valgrind.org/docs/manual/hg-manual.html) 35 | 36 | Produces false positives except for `pthread_spinlock_t` 37 | 38 | 39 | ## Benchmark arbitrary times 40 | | Release build | 1 core | 2 cores | 3 cores | 4 cores | 41 | | ---------------------- | --------| ------- | -------- | ------- | 42 | | audio_spin_mutex | 3.90 ms | 1.86 ms | 13.7 ms | 92.1 ms | 43 | | spinlock_atomic_bool | 1.22 ms | 3.16 ms | 21.2 ms | 52.2 ms | 44 | | spinlock_atomic_flag | 1.89 ms | 3.09 ms | 9.86 ms | 39.0 ms | 45 | | spinlock_atomic_flag_c | 2.53 ms | 7.86 ms | 36.8 ms | 106.0 ms | 46 | | spinlock_posix_c | 1.47 ms | 2.96 ms | 18.4 ms | 60.1 ms | 47 | 48 | * 49 | * 50 | 51 | ## References 52 | * [Wikipedia Spinlock](https://en.wikipedia.org/wiki/Spinlock) 53 | * [OSDev Spinlock](https://wiki.osdev.org/Spinlock) 54 | * [Using locks in real-time audio processing, safely](https://timur.audio/using-locks-in-real-time-audio-processing-safely) 55 | * [Implementing a spinlock in c++](https://medium.com/@amishav/implementing-a-spinlock-in-c-8078ec584efc) 56 | * [Correctly implementing a spinlock in C++](https://rigtorp.se/spinlock/) 57 | * [Spinlocks - Part 7](https://coffeebeforearch.github.io/2020/11/07/spinlocks-7.html) 58 | * 59 | 60 | -------------------------------------------------------------------------------- /Spinlock/audio_spin_mutex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(audio_spin_mutex) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Spinlock/audio_spin_mutex/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // https://timur.audio/using-locks-in-real-time-audio-processing-safely 9 | struct audio_spin_mutex { 10 | void lock() noexcept { 11 | // approx. 5x5 ns (= 25 ns), 10x40 ns (= 400 ns), and 3000x350 ns 12 | // (~ 1 ms), respectively, when measured on a 2.9 GHz Intel i9 13 | constexpr std::array iterations = {5, 10, 3000}; 14 | 15 | for (int i = 0; i < iterations[0]; ++i) { 16 | if (try_lock()) return; 17 | } 18 | 19 | for (int i = 0; i < iterations[1]; ++i) { 20 | if (try_lock()) return; 21 | 22 | _mm_pause(); 23 | } 24 | 25 | while (true) { 26 | for (int i = 0; i < iterations[2]; ++i) { 27 | if (try_lock()) return; 28 | 29 | _mm_pause(); 30 | _mm_pause(); 31 | _mm_pause(); 32 | _mm_pause(); 33 | _mm_pause(); 34 | _mm_pause(); 35 | _mm_pause(); 36 | _mm_pause(); 37 | _mm_pause(); 38 | _mm_pause(); 39 | } 40 | 41 | // waiting longer than we should, let's give other threads 42 | // a chance to recover 43 | std::this_thread::yield(); 44 | } 45 | } 46 | 47 | bool try_lock() noexcept { 48 | return !flag.test_and_set(std::memory_order_acquire); 49 | } 50 | 51 | void unlock() noexcept { flag.clear(std::memory_order_release); } 52 | 53 | private: 54 | std::atomic_flag flag = ATOMIC_FLAG_INIT; 55 | }; 56 | 57 | audio_spin_mutex lock; 58 | 59 | void increment_counter(int64_t& counter) { 60 | for (int i = 0; i < 10000; i++) { 61 | lock.lock(); 62 | counter++; 63 | lock.unlock(); 64 | } 65 | } 66 | 67 | int main() { 68 | int64_t counter = 0; 69 | 70 | std::thread thread1 = std::thread(increment_counter, std::ref(counter)); 71 | std::thread thread2 = std::thread(increment_counter, std::ref(counter)); 72 | 73 | thread1.join(); 74 | thread2.join(); 75 | 76 | std::cout << "Sum: " << counter << std::endl; 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /Spinlock/benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(spinlock_benchmarks) 4 | 5 | find_package(benchmark REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | target_link_libraries( 28 | ${PROJECT_NAME} 29 | PRIVATE 30 | benchmark::benchmark_main 31 | ) 32 | -------------------------------------------------------------------------------- /Spinlock/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Build instructions 2 | ``` 3 | mkdir build 4 | cd build 5 | conan install .. --build=missing 6 | cd Release/ 7 | cmake ../.. -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=./generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release 8 | make 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /Spinlock/benchmarks/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | benchmark/1.9.0 3 | 4 | [generators] 5 | CMakeToolchain 6 | CMakeDeps 7 | 8 | [layout] 9 | cmake_layout 10 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_bool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(spinlock_atomic_bool) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_bool/main.cpp: -------------------------------------------------------------------------------- 1 | // https://rigtorp.se/spinlock/ 2 | // sudo perf stat -e 3 | // L1-dcache-loads,L1-dcache-load-misses,mem_inst_retired.lock_loads ./spinlock 4 | #include 5 | #include 6 | 7 | class SpinLock { 8 | public: 9 | void lock() noexcept { 10 | for (;;) { 11 | // Optimistically assume the lock is free on the first try 12 | if (!lock_.exchange(true, std::memory_order_acquire)) { 13 | return; 14 | } 15 | // Wait for lock to be released without generating cache misses 16 | while (lock_.load(std::memory_order_relaxed)) { 17 | // Issue X86 PAUSE or ARM YIELD instruction to reduce contention between 18 | // hyper-threads 19 | __builtin_ia32_pause(); 20 | } 21 | } 22 | } 23 | 24 | bool try_lock() noexcept { 25 | // First do a relaxed load to check if lock is free in order to prevent 26 | // unnecessary cache misses if someone does while(!try_lock()) 27 | return !lock_.load(std::memory_order_relaxed) && 28 | !lock_.exchange(true, std::memory_order_acquire); 29 | } 30 | 31 | void unlock() noexcept { lock_.store(false, std::memory_order_release); } 32 | 33 | private: 34 | std::atomic lock_{false}; 35 | }; 36 | 37 | SpinLock lock; 38 | 39 | void increment_counter(int64_t& counter) { 40 | for (int i = 0; i < 10000; i++) { 41 | lock.lock(); 42 | counter++; 43 | lock.unlock(); 44 | } 45 | } 46 | 47 | int main() { 48 | int64_t counter = 0; 49 | 50 | std::thread thread1 = std::thread(increment_counter, std::ref(counter)); 51 | std::thread thread2 = std::thread(increment_counter, std::ref(counter)); 52 | 53 | thread1.join(); 54 | thread2.join(); 55 | 56 | std::cout << "Sum: " << counter << std::endl; 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_flag/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(spinlock_atomic_flag) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_compile_options( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | $<$:/W4 /WX> 24 | $<$>:-Wall -Wextra -Wpedantic> 25 | ) 26 | 27 | if (USE_TSAN) 28 | target_compile_options( 29 | ${PROJECT_NAME} 30 | PRIVATE 31 | $<$:-fsanitize=thread> 32 | ) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE 37 | $<$:-fsanitize=thread> 38 | ) 39 | endif() 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_flag/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class SpinLock { 5 | public: 6 | void lock() noexcept { 7 | while (mFlag.test_and_set(std::memory_order_acquire)) { 8 | mFlag.wait(true, std::memory_order_relaxed); 9 | } 10 | } 11 | 12 | void unlock() noexcept { 13 | mFlag.clear(std::memory_order_release); 14 | mFlag.notify_one(); 15 | } 16 | 17 | private: 18 | std::atomic_flag mFlag = ATOMIC_FLAG_INIT; 19 | }; 20 | 21 | SpinLock lock; 22 | 23 | void increment_counter(int64_t& counter) { 24 | for (int i = 0; i < 10000; i++) { 25 | lock.lock(); 26 | counter++; 27 | lock.unlock(); 28 | } 29 | } 30 | 31 | int main() { 32 | int64_t counter = 0; 33 | 34 | std::thread thread1 = std::thread(increment_counter, std::ref(counter)); 35 | std::thread thread2 = std::thread(increment_counter, std::ref(counter)); 36 | 37 | thread1.join(); 38 | thread2.join(); 39 | 40 | std::cout << "Sum: " << counter << std::endl; 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_flag_c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(spinlock_atomic_flag_c LANGUAGES C) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.c 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | # C_STANDARD 11 16 | C_STANDARD_REQUIRED YES 17 | C_EXTENSIONS NO 18 | LINKER_LANGUAGE C 19 | ) 20 | 21 | target_compile_options( 22 | ${PROJECT_NAME} 23 | PRIVATE 24 | $<$:/W4 /WX> 25 | $<$>:-Wall -Wextra -Wpedantic> 26 | ) 27 | 28 | if (USE_TSAN) 29 | target_compile_options( 30 | ${PROJECT_NAME} 31 | PRIVATE 32 | $<$:-fsanitize=thread> 33 | ) 34 | 35 | target_link_libraries( 36 | ${PROJECT_NAME} 37 | PRIVATE 38 | $<$:-fsanitize=thread> 39 | ) 40 | endif() 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Spinlock/spinlock_atomic_flag_c/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | https://wiki.osdev.org/Spinlock 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | atomic_flag sl = ATOMIC_FLAG_INIT; 11 | 12 | void acquire(atomic_flag* lock) { 13 | while (atomic_flag_test_and_set_explicit(lock, memory_order_acquire)) { 14 | /* use whatever is appropriate for your target arch here */ 15 | __builtin_ia32_pause(); 16 | } 17 | } 18 | 19 | void release(atomic_flag* lock) { 20 | atomic_flag_clear_explicit(lock, memory_order_release); 21 | } 22 | 23 | void* inc(void* arg) { 24 | int64_t* val = (int64_t*)arg; 25 | for (int i = 0; i < 100000; i++) { 26 | acquire(&sl); 27 | *val = *val + 1; 28 | release(&sl); 29 | } 30 | 31 | return NULL; 32 | } 33 | 34 | int main(void) { 35 | int64_t val = 0; 36 | 37 | pthread_t t1, t2; 38 | 39 | pthread_create(&t1, NULL, inc, &val); 40 | pthread_create(&t2, NULL, inc, &val); 41 | 42 | pthread_join(t1, NULL); 43 | pthread_join(t2, NULL); 44 | 45 | printf("Sum: %ld\n", val); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /Spinlock/spinlock_posix_c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(spinlock_posix_c LANGUAGES C) 4 | 5 | option(USE_TSAN "Use Thread Sanitizer" OFF) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | main.c 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | # C_STANDARD 11 16 | C_STANDARD_REQUIRED YES 17 | C_EXTENSIONS NO 18 | LINKER_LANGUAGE C 19 | ) 20 | 21 | target_compile_options( 22 | ${PROJECT_NAME} 23 | PRIVATE 24 | $<$:/W4 /WX> 25 | $<$>:-Wall -Wextra -Wpedantic> 26 | ) 27 | 28 | if (USE_TSAN) 29 | target_compile_options( 30 | ${PROJECT_NAME} 31 | PRIVATE 32 | $<$:-fsanitize=thread> 33 | ) 34 | 35 | target_link_libraries( 36 | ${PROJECT_NAME} 37 | PRIVATE 38 | $<$:-fsanitize=thread> 39 | ) 40 | endif() 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Spinlock/spinlock_posix_c/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | https://coffeebeforearch.github.io/2020/11/07/spinlocks-7.html 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | pthread_spinlock_t sl; 10 | 11 | void* inc(void* arg) { 12 | int64_t* val = (int64_t*)arg; 13 | for (int i = 0; i < 100000; i++) { 14 | pthread_spin_lock(&sl); 15 | *val = *val + 1; 16 | pthread_spin_unlock(&sl); 17 | } 18 | 19 | return NULL; 20 | } 21 | 22 | int main(void) { 23 | pthread_spin_init(&sl, PTHREAD_PROCESS_PRIVATE); 24 | 25 | int64_t val = 0; 26 | 27 | pthread_t t1, t2; 28 | 29 | pthread_create(&t1, NULL, inc, &val); 30 | pthread_create(&t2, NULL, inc, &val); 31 | 32 | pthread_join(t1, NULL); 33 | pthread_join(t2, NULL); 34 | 35 | printf("Sum: %ld\n", val); 36 | 37 | pthread_spin_destroy(&sl); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /StrategizedLocking/Concepts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(StrategizedLockingConcepts) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | strategizedLockingCompileTimeWithConcepts.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /StrategizedLocking/Concepts/strategizedLockingCompileTimeWithConcepts.cpp: -------------------------------------------------------------------------------- 1 | // strategizedLockingCompileTimeWithConcepts.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | concept BasicLockable = requires(T lo) { 9 | lo.lock(); 10 | lo.unlock(); 11 | }; 12 | 13 | template 14 | class StrategizedLocking { 15 | Lock& lock; 16 | public: 17 | StrategizedLocking(Lock& l): lock(l){ 18 | lock.lock(); 19 | } 20 | ~StrategizedLocking(){ 21 | lock.unlock(); 22 | } 23 | }; 24 | 25 | struct NullObjectMutex { 26 | void lock(){} 27 | void unlock(){} 28 | }; 29 | 30 | class NoLock{ 31 | public: 32 | void lock() const { 33 | std::cout << "NoLock::lock " << '\n'; 34 | nullObjectMutex.lock(); 35 | } 36 | void unlock() const { 37 | std::cout << "NoLock::unlock " << '\n'; 38 | nullObjectMutex.lock(); 39 | } 40 | mutable NullObjectMutex nullObjectMutex; 41 | }; 42 | 43 | class ExclusiveLock { 44 | public: 45 | void lock() const { 46 | std::cout << " ExclusiveLock::lock " << '\n'; 47 | mutex.lock(); 48 | } 49 | void unlock() const { 50 | std::cout << " ExclusiveLock::unlock " << '\n'; 51 | mutex.unlock(); 52 | } 53 | mutable std::mutex mutex; 54 | }; 55 | 56 | class SharedLock { 57 | public: 58 | void lock() const { 59 | std::cout << " SharedLock::lock " << '\n'; 60 | sharedMutex.lock_shared(); 61 | } 62 | void unlock() const { 63 | std::cout << " SharedLock::unlock " << '\n'; 64 | sharedMutex.unlock_shared(); 65 | } 66 | mutable std::shared_mutex sharedMutex; 67 | }; 68 | 69 | int main() { 70 | 71 | std::cout << '\n'; 72 | 73 | NoLock noLock; 74 | StrategizedLocking stratLock1{noLock}; 75 | 76 | { 77 | ExclusiveLock exLock; 78 | StrategizedLocking stratLock2{exLock}; 79 | { 80 | SharedLock sharLock; 81 | StrategizedLocking startLock3{sharLock}; 82 | } 83 | } 84 | 85 | std::cout << '\n'; 86 | 87 | } 88 | -------------------------------------------------------------------------------- /StrategizedLocking/README.md: -------------------------------------------------------------------------------- 1 | # Strategized Locking 2 | 3 | * Dealing with Mutation Pattern 4 | 5 | * Enables it to use various locking strategies as replaceable 6 | components. 7 | 8 | * Is the application of the strategy pattern to locking. 9 | 10 | ## Idea 11 | 12 | * You want to use your library in various domains. 13 | * Depending on the domain, you want to use exclusive locking, 14 | shared locking, or no locking. 15 | * Configure your locking strategy at compile time or run time. 16 | 17 | ## Advantages 18 | 19 | * Run-time polymorphism 20 | * Enables it to change the locking strategy during run time. 21 | * Compile-time polymorphism 22 | * No cost at run time 23 | * Producer flatters object hierarchies 24 | 25 | ## Disadvantages 26 | 27 | * Run-time polymorphism 28 | * Needs a pointer indirection. 29 | * Compile-time polymorphism 30 | * Produces in the error case a quite challenging to understand error message (when no concepts are used) 31 | -------------------------------------------------------------------------------- /StrategizedLocking/Runtime/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(StrategizedLockingRuntime) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | strategizedLockingRuntime.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /StrategizedLocking/Runtime/strategizedLockingRuntime.cpp: -------------------------------------------------------------------------------- 1 | // strategizedLockingRuntime.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Lock { 8 | public: 9 | virtual void lock() const = 0; 10 | virtual void unlock() const = 0; 11 | }; 12 | 13 | class StrategizedLocking { 14 | Lock& lock; 15 | public: 16 | StrategizedLocking(Lock& l): lock(l){ 17 | lock.lock(); 18 | } 19 | ~StrategizedLocking(){ 20 | lock.unlock(); 21 | } 22 | }; 23 | 24 | struct NullObjectMutex{ 25 | void lock(){} 26 | void unlock(){} 27 | }; 28 | 29 | class NoLock : public Lock { 30 | void lock() const override { 31 | std::cout << "NoLock::lock " << '\n'; 32 | nullObjectMutex.lock(); 33 | } 34 | void unlock() const override { 35 | std::cout << "NoLock::unlock " << '\n'; 36 | nullObjectMutex.unlock(); 37 | } 38 | mutable NullObjectMutex nullObjectMutex; 39 | }; 40 | 41 | class ExclusiveLock : public Lock { 42 | void lock() const override { 43 | std::cout << " ExclusiveLock::lock " << '\n'; 44 | mutex.lock(); 45 | } 46 | void unlock() const override { 47 | std::cout << " ExclusiveLock::unlock " << '\n'; 48 | mutex.unlock(); 49 | } 50 | mutable std::mutex mutex; 51 | }; 52 | 53 | class SharedLock : public Lock { 54 | void lock() const override { 55 | std::cout << " SharedLock::lock " << '\n'; 56 | sharedMutex.lock_shared(); 57 | } 58 | void unlock() const override { 59 | std::cout << " SharedLock::unlock " << '\n'; 60 | sharedMutex.unlock_shared(); 61 | } 62 | mutable std::shared_mutex sharedMutex; 63 | }; 64 | 65 | int main() { 66 | 67 | std::cout << '\n'; 68 | 69 | NoLock noLock; 70 | StrategizedLocking stratLock1{noLock}; 71 | 72 | { 73 | ExclusiveLock exLock; 74 | StrategizedLocking stratLock2{exLock}; 75 | { 76 | SharedLock sharLock; 77 | StrategizedLocking startLock3{sharLock}; 78 | } 79 | } 80 | 81 | std::cout << '\n'; 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /ThreadSafeInterface/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ThreadSafeInterface) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | threadSafeInterface.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /ThreadSafeInterface/README.md: -------------------------------------------------------------------------------- 1 | # Thread-Safe Interface 2 | 3 | * Dealing with Mutation Pattern 4 | 5 | * The thread-safe interface extends the critical region to an 6 | object. 7 | 8 | ## Antipattern: Each member function uses a lock internally 9 | 10 | * The performance of the system goes down. 11 | * Deadlocks appear when two member functions call each other. 12 | 13 | ## Solutions: 14 | 15 | * All interface member functions (public) use a lock. 16 | 17 | * All implementation member functions (protected and 18 | private) must not use a lock. 19 | 20 | * The interface member functions call only implementation member 21 | functions. 22 | -------------------------------------------------------------------------------- /ThreadSafeInterface/threadSafeInterface.cpp: -------------------------------------------------------------------------------- 1 | // threadSafeInterface.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Critical{ 8 | 9 | public: 10 | void interface1() const { 11 | std::lock_guard lockGuard(mut); 12 | implementation1(); 13 | } 14 | 15 | void interface2(){ 16 | std::lock_guard lockGuard(mut); 17 | implementation2(); 18 | implementation3(); 19 | implementation1(); 20 | } 21 | 22 | private: 23 | void implementation1() const { 24 | std::cout << "implementation1: " 25 | << std::this_thread::get_id() << '\n'; 26 | } 27 | void implementation2(){ 28 | std::cout << " implementation2: " 29 | << std::this_thread::get_id() << '\n'; 30 | } 31 | void implementation3(){ 32 | std::cout << " implementation3: " 33 | << std::this_thread::get_id() << '\n'; 34 | } 35 | 36 | 37 | mutable std::mutex mut; 38 | 39 | }; 40 | 41 | int main(){ 42 | 43 | std::cout << '\n'; 44 | 45 | std::thread t1([]{ 46 | const Critical crit; 47 | crit.interface1(); 48 | }); 49 | 50 | std::thread t2([]{ 51 | Critical crit; 52 | crit.interface2(); 53 | crit.interface1(); 54 | }); 55 | 56 | Critical crit; 57 | crit.interface1(); 58 | crit.interface2(); 59 | 60 | t1.join(); 61 | t2.join(); 62 | 63 | std::cout << '\n'; 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /ThreadSpecificStorage/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(ThreadSpecificStorage) 4 | 5 | find_package(Threads REQUIRED) 6 | 7 | add_executable( 8 | ${PROJECT_NAME} 9 | threadLocalSummation.cpp 10 | ) 11 | 12 | set_target_properties( 13 | ${PROJECT_NAME} 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | ) 19 | 20 | target_link_libraries( 21 | ${PROJECT_NAME} 22 | PRIVATE 23 | Threads::Threads 24 | ) 25 | -------------------------------------------------------------------------------- /ThreadSpecificStorage/README.md: -------------------------------------------------------------------------------- 1 | # Thread-Specific Storage 2 | 3 | * Dealing with Sharing Pattern 4 | 5 | * Thread-specific storage enables global state within a 6 | thread. 7 | 8 | ## Typical use-cases: 9 | 10 | * Porting a single-thread to multithreaded program 11 | 12 | * Compute thread-local and share the results 13 | 14 | * Thread-local logger 15 | -------------------------------------------------------------------------------- /ThreadSpecificStorage/threadLocalSummation.cpp: -------------------------------------------------------------------------------- 1 | // threadLocalSummation.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | constexpr long long size = 100000000; 12 | 13 | constexpr long long fir = 25000000; 14 | constexpr long long sec = 50000000; 15 | constexpr long long thi = 75000000; 16 | constexpr long long fou = 100000000; 17 | 18 | thread_local unsigned long long tmpSum = 0; 19 | 20 | void sumUp(std::atomic& sum, const std::vector& val, 21 | unsigned long long beg, unsigned long long end){ 22 | for (auto i = beg; i < end; ++i){ 23 | tmpSum += val[i]; 24 | } 25 | sum.fetch_add(tmpSum, std::memory_order_relaxed); 26 | } 27 | 28 | int main(){ 29 | 30 | std::cout << '\n'; 31 | 32 | std::vector randValues; 33 | randValues.reserve(size); 34 | 35 | std::mt19937 engine; 36 | std::uniform_int_distribution<> uniformDist(1, 10); 37 | for (long long i = 0; i < size; ++i) 38 | randValues.push_back(uniformDist(engine)); 39 | 40 | std::atomic sum{}; 41 | const auto sta = std::chrono::system_clock::now(); 42 | 43 | std::thread t1(sumUp, std::ref(sum), std::ref(randValues), 0, fir); 44 | std::thread t2(sumUp, std::ref(sum), std::ref(randValues), fir, sec); 45 | std::thread t3(sumUp, std::ref(sum), std::ref(randValues), sec, thi); 46 | std::thread t4(sumUp, std::ref(sum), std::ref(randValues), thi, fou); 47 | 48 | t1.join(); 49 | t2.join(); 50 | t3.join(); 51 | t4.join(); 52 | 53 | const std::chrono::duration dur= 54 | std::chrono::system_clock::now() - sta; 55 | 56 | std::cout << "Time for addition " << dur.count() 57 | << " seconds" << '\n'; 58 | std::cout << "Result: " << sum << '\n'; 59 | 60 | std::cout << '\n'; 61 | 62 | } 63 | 64 | --------------------------------------------------------------------------------