├── README.md ├── advanced-threads-com.cpp ├── banner.png ├── basic-thread-safe-stack.cpp ├── basic-threads-com.cpp ├── build-and-args-threads.cpp ├── custom-parallel-find-1.cpp ├── multithread-single-exec.cpp ├── raii-thread-wrapper.cpp ├── std-async-example.cpp ├── std-packaged-task-basics.cpp ├── stl-algorithms-policies.cpp ├── thread-pool ├── CMakeLists.txt ├── inc │ └── threadpool.h ├── main.cpp └── src │ └── threadpool.cpp ├── thread-safe-cout-wrapper.cpp ├── thread_safe_queue_example.cpp ├── transfer-ownership-threads.cpp └── useful-operations-threads.cpp /README.md: -------------------------------------------------------------------------------- 1 | # ![concurrency-banner](banner.png) 2 | 3 | ## Concurrency in modern C++ ![Language](https://img.shields.io/badge/language-C++17-orange.svg) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.md) 4 | 5 | This repository contains a list of small experiments for **modern c++ concurrency**. 6 | 7 | ## The basics 8 | 9 | These experiments address **basic questions** such as : 10 | - [What are the possible ways to build a thread ?](build-and-args-threads.cpp) 11 | - [How to pass parameters to a thread ?](build-and-args-threads.cpp) 12 | - [How to transfer the ownership of a thread ?](transfer-ownership-threads.cpp) 13 | - [What are the useful operations on threads ?](useful-operations-threads.cpp) 14 | - [Ensure single execution in multithreaded context ?](multithread-single-exec.cpp) 15 | - ... 16 | 17 | ## Some tests on more advanced topics 18 | 19 | It should also provide sample code examples for _more advanced_ topics such as : 20 | - [RAII Thread wrapper](raii-thread-wrapper.cpp) 21 | - [Thread-safe Cout wrapper](thread-safe-cout-wrapper.cpp) 22 | - Communication between threads... 23 | - [using mutex and condition_variable](basic-threads-com.cpp) 24 | - [using promise and shared_future](advanced-threads-com.cpp) 25 | - [Thread-safe Stack : Access to shared data and locking mechanisms](basic-thread-safe-stack.cpp) 26 | - [Thread safe queue : Using condition_variables](thread_safe_queue_example.cpp) 27 | - [Background tasks using std::async](std-async-example.cpp) 28 | - [Usage of std::packaged_task](std-packaged-task-basics.cpp) 29 | - Lock-based thread-safe data structures and algorithms 30 | - [custom std::find implementation using std::packaged_task](custom-parallel-find-1.cpp) 31 | - [Thread pools](thread-pool/) 32 | - ... 33 | 34 | ## The latest features 35 | 36 | I will also try to understand the **enhancements of the latest c++ standards** C++17/C++20. 37 | 38 | - [STL algorithms execution policy](stl-algorithms-policies.cpp) 39 | - JThread 40 | - [Usage example](jthread-basics.cpp) 41 | - [Custom JThread implementation](jthread-custom.cpp) 42 | - Coroutines 43 | 44 | 45 | ## Ressources 46 | 47 | This repository is highly inspirated by books and online ressources such as : 48 | - [**C++ concurrency in action**](https://www.manning.com/books/c-plus-plus-concurrency-in-action). 49 | - [**Effective Modern C++**](https://www.oreilly.com/library/view/effective-modern-c/9781491908419/). 50 | - [**Effective C++**](https://www.eyrolles.com/Informatique/Livre/effective-c--9780321334879/). 51 | - [**C++17 in detail**](https://leanpub.com/cpp17indetail?utm_source=blog&utm_campaign=adside) 52 | 53 | Of course, the ressources used will be quoted in each sample code. 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /advanced-threads-com.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * COMMUNICATIONS BETWEEN THREADS USING * 3 | * STD::PROMISE AND STD::FUTURE * 4 | ************************************************************/ 5 | 6 | /*! 7 | * @brief 8 | * 9 | * std::promise allows you to store a value (or exception) 10 | * that can be later acquired asynchronously using std::future. 11 | * 12 | * A std::promise contains a shared state that can be : 13 | * - Not evaluated yet. 14 | * - Evaluated to a value. 15 | * - Evaluated to an exception. 16 | * 17 | * A std::promise can do 3 things with its shared state : 18 | * - make ready : stores the result/exception into the shared state, 19 | * make it ready and unblocks any thread waiting on a std::future 20 | * associated with the shared state. 21 | * - release : gives up its reference to the shared state. 22 | * If it was the last reference, the shared state is destroyed. 23 | * - abandon : stores an exception of type std::future_error 24 | * withh type std::future_errc::broken_promise and make its 25 | * shared state ready, and then release it. 26 | * 27 | * See https://en.cppreference.com/w/cpp/thread/promise 28 | * and https://en.cppreference.com/w/cpp/thread/future 29 | * for more details. 30 | */ 31 | 32 | /*! 33 | * @note When to use ? 34 | * 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | /*! 50 | * @brief coutWrapper 51 | * Thread-safe cout wrapper. 52 | */ 53 | class coutWrapper : public std::stringstream { 54 | public: 55 | coutWrapper() = default; 56 | ~coutWrapper() { 57 | std::lock_guard l_lck(m_mtx); 58 | std::cout << std::stringstream::rdbuf(); 59 | std::cout.flush(); 60 | } 61 | 62 | private: 63 | static inline std::mutex m_mtx; 64 | }; 65 | 66 | /*! 67 | * @brief flip_map 68 | * Helper function for T1. 69 | */ 70 | template 71 | std::multimap flip_map(const std::map &src) 72 | { 73 | std::multimap l_ret; 74 | std::transform(src.begin(), src.end(), std::inserter(l_ret, l_ret.begin()), 75 | [](const auto &p) { return std::make_pair(p.second, p.first); }); 76 | return l_ret; 77 | } 78 | 79 | /*! 80 | * @brief T1_job 81 | * compute a sorted histogram of characters 82 | * in a given shared std::string. 83 | */ 84 | std::multimap T1_job( std::shared_future& p_input ) 85 | { 86 | coutWrapper{} << "T1 - Waiting for T2 to compute the value...\n"; 87 | 88 | std::map l_ret; 89 | 90 | // future::get() will wait until the future has 91 | // a valid result and retrieves it. 92 | if ( !p_input.valid() ) 93 | { 94 | throw std::future_errc::no_state; 95 | } 96 | auto l_input = p_input.get(); 97 | 98 | coutWrapper{} << "T1 - Now has access to the value!\n"; 99 | 100 | for ( const char& c : l_input ) { ++l_ret[std::tolower(c)]; } 101 | 102 | coutWrapper{} << "T1 - ended.\n"; 103 | 104 | return flip_map(l_ret); 105 | } 106 | 107 | /*! 108 | * @brief T2_job 109 | * Compute a sorted copy of an input string. 110 | */ 111 | void T2_job( const std::string& p_input, 112 | std::promise p_promise ) 113 | { 114 | coutWrapper{} << "T2 - Computing the shared-state value...\n"; 115 | // Adding artificial delay for clarity 116 | std::this_thread::sleep_for( std::chrono::milliseconds(2000) ); 117 | 118 | std::string l_ret; 119 | std::transform( std::begin(p_input), 120 | std::end (p_input), 121 | std::back_inserter(l_ret), 122 | [](const char& c){ return std::tolower(c); }); 123 | 124 | std::sort( std::begin(l_ret), std::end(l_ret) ); 125 | 126 | coutWrapper{} << "T2 - set the value of the promise to '" 127 | << l_ret << "'\n"; 128 | 129 | // This will notify the associated std::shared_future 130 | // and the threads waiting for it to be ready. 131 | p_promise.set_value(l_ret); 132 | 133 | // You can keep doing some work 134 | std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 135 | coutWrapper{} << "T2 - ended.\n"; 136 | } 137 | 138 | /*! 139 | * @brief T3_job 140 | * Compute the number of vowels in an input string 141 | * computed by another thread using std::shared_future. 142 | */ 143 | size_t T3_job( std::shared_future& p_input ) 144 | { 145 | static char vowels[] {"aeiouy"}; 146 | 147 | coutWrapper{} << "T3 - Waiting for T2 to compute the value...\n"; 148 | 149 | // future::get() will wait until the future has 150 | // a valid result and retrieves it. 151 | if (!p_input.valid()) 152 | { 153 | throw std::future_errc::no_state; 154 | } 155 | auto l_input = p_input.get(); 156 | 157 | coutWrapper{} << "T3 - Now has access to the value!\n"; 158 | 159 | size_t l_ret = std::count_if( std::begin(l_input), 160 | std::end (l_input), 161 | [](const char& c){ 162 | return std::find( std::begin(vowels), 163 | std::end (vowels), 164 | std::tolower(c) ) != std::end(vowels); } ); 165 | 166 | coutWrapper{} << "T3 - ended.\n"; 167 | 168 | return l_ret; 169 | } 170 | 171 | int main() 172 | { 173 | std::string inputStr { "Hello beautiful World! Nice to meet you!" }; 174 | 175 | std::promise T2_promise; 176 | std::shared_future T2_shared_future = T2_promise.get_future(); 177 | 178 | auto T1(std::async(std::launch::async, 179 | T1_job, 180 | std::ref(T2_shared_future))); // std::future> 181 | std::thread T2( T2_job, std::ref(inputStr), std::move(T2_promise) ); 182 | std::thread T3( T3_job, std::ref(T2_shared_future) ); 183 | 184 | T2.join(); 185 | T3.join(); 186 | 187 | coutWrapper{} << "T1 is the result of a std::task, but it " 188 | "works exctly the same as std::thread T3\n"; 189 | // Blocking untill T1 is available. 190 | // Note that can lead to deadlocks (for example if we put this 191 | // before joining T2...) 192 | for (const auto &[c, nb] : T1.get() ) 193 | { 194 | coutWrapper{} << c << nb; 195 | } 196 | 197 | return EXIT_SUCCESS; 198 | } 199 | 200 | /* 201 | T2 - Computing the shared-state value... 202 | T1 - Waiting for T2 to compute the value... 203 | T3 - Waiting for T2 to compute the value... 204 | T2 - set the value of the promise to ' !!abcdeeeeefhiillllmnoooortttuuuwy' 205 | T1 - Now has access to the value! 206 | T3 - Now has access to the value! 207 | T1 - ended. 208 | T3 - ended. 209 | T2 - ended. 210 | T1 is the result of a std::task, but it works exctly the same as std::thread T3 211 | 1a1b1c1d1f1h1m1n1r1w1y2i2!3t3u4o4l5e6 212 | */ -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MericLuc/Modern-cpp-concurrency/8c5b2582aab10dfedf9b90db26e04fc1c08a2c62/banner.png -------------------------------------------------------------------------------- /basic-thread-safe-stack.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * BASIC EXAMPLE OF A THREAD SAFE STACK * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /*! 14 | * @brief This (almost) achieves thread-safety 15 | * but limits parallelism alot because 16 | * only one thread can operate on the structure 17 | * at a given time ! 18 | */ 19 | 20 | /*! 21 | * @note There are still race conditions 22 | * that are inherited from interface ! 23 | * 24 | * - Between empty() and top() 25 | * - Between top() and pop() 26 | * 27 | * The method maybe_pop_top() has been introduced 28 | * to fight these race conditions. 29 | */ 30 | 31 | template < typename T > 32 | class stackThreadSafe { 33 | public: 34 | stackThreadSafe() : m_data(), m_mutx() {} 35 | stackThreadSafe(const stackThreadSafe& p_other) : 36 | m_data(p_other.m_data), m_mutx() {} 37 | stackThreadSafe& operator=(const stackThreadSafe& p_other) { 38 | if ( &p_other == this ) 39 | return *this; 40 | m_data = p_other.m_data(); 41 | 42 | return *this; 43 | } 44 | 45 | void push( T&& p_val ) { 46 | const std::lock_guard l_lck(m_mutx); 47 | m_data.push(p_val); 48 | } 49 | 50 | void pop() { 51 | const std::lock_guard l_lck(m_mutx); 52 | m_data.pop(); 53 | } 54 | 55 | std::size_t size() const { 56 | const std::lock_guard l_lck(m_mutx); 57 | return m_data.size(); 58 | } 59 | 60 | T& top() { 61 | const std::lock_guard l_lck(m_mutx); 62 | return m_data.top(); 63 | } 64 | 65 | std::optional maybe_pop_top() { 66 | const std::lock_guard l_lck(m_mutx); 67 | 68 | if ( m_data.empty() ) return std::nullopt; 69 | 70 | std::optional l_ret{ m_data.top() }; 71 | m_data.pop(); 72 | return l_ret; 73 | } 74 | 75 | const T& top() const { 76 | const std::lock_guard l_lck(m_mutx); 77 | return m_data.top(); 78 | } 79 | 80 | private: 81 | std::stack m_data; 82 | std::mutex m_mutx; 83 | }; 84 | 85 | int main() 86 | { 87 | uint32_t THREADS_NB{3}, PUSH_NB{5}, POP_NB{5}; 88 | 89 | stackThreadSafe myStack; 90 | std::vector producers, consumers; 91 | std::mutex iomutex; 92 | 93 | // THREADS_NB Producers pushing PUSH_NB times to the stack 94 | for ( uint32_t id = 0; id < THREADS_NB; id++ ) 95 | { 96 | producers.push_back( std::thread( [&, id]() { 97 | for ( int i = 0; i < PUSH_NB; i++ ) 98 | { 99 | // Locked I/O for output clarity 100 | { 101 | const std::lock_guard l_lck(iomutex); 102 | std::cout << "T" << id << ": pushed " << ( id * THREADS_NB + i ) << "\n"; 103 | } 104 | myStack.push( id * THREADS_NB + i ); // push a unique val 105 | } 106 | } ) ); 107 | } 108 | 109 | // THREADS_NB consumers poping POP_NB times to the stack 110 | for (uint32_t id = THREADS_NB; id < (THREADS_NB << 1); id++) 111 | { 112 | consumers.push_back( std::thread([&, id]() { 113 | for (int i = 0; i < POP_NB; i++) 114 | { 115 | auto curTop = myStack.maybe_pop_top(); 116 | // Locked I/O for output clarity 117 | { 118 | const std::lock_guard l_lck(iomutex); 119 | std::cout << "T" << id << ": popped " << ( curTop ? std::to_string(*curTop) : "nothing" ) << "\n"; 120 | } 121 | } 122 | })); 123 | } 124 | 125 | for ( auto& t : producers ) 126 | t.join(); 127 | 128 | for ( auto& t : consumers ) 129 | t.join(); 130 | 131 | return EXIT_SUCCESS; 132 | } -------------------------------------------------------------------------------- /basic-threads-com.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * BASIC COMMUNICATION BETWEEN THREADS * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /*! 12 | * @brief We will use condition variables 13 | * to perform basic communication 14 | * between threads. 15 | * 16 | * @note A condition variable is an object able to block 17 | * the calling thread until notified to resume. 18 | * 19 | * More infos here 20 | * http://www.cplusplus.com/reference/condition_variable/condition_variable/ 21 | */ 22 | 23 | std::mutex mtx; 24 | std::condition_variable cv; 25 | std::string data; 26 | bool isReady{false}; 27 | bool isDone {false}; 28 | 29 | void workImpl() 30 | { 31 | std::unique_lock lck(mtx); 32 | cv.wait( lck, []{ return isReady; } ); 33 | 34 | std::cout << "Producer thread is processing data\n"; 35 | 36 | // Do your work 37 | // ... 38 | std::this_thread::sleep_for( std::chrono::milliseconds(2000) ); 39 | data = "Processing result"; 40 | isDone = true; 41 | 42 | std::cout << "Producer thread completed!\n"; 43 | 44 | lck.unlock(); 45 | cv.notify_one(); 46 | } 47 | 48 | void aFunction() 49 | { 50 | std::cout << "Consumer thread waiting for producer to complete its task...\n"; 51 | { 52 | std::unique_lock lck(mtx); 53 | cv.wait( lck, []{ return isDone; } ); 54 | } 55 | std::cout << "Consumer thread knows producer is done\n"; 56 | // Do anything you want 57 | // ... 58 | std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 59 | std::cout << "Consumer thread done!\n"; 60 | } 61 | 62 | int main() 63 | { 64 | std::thread producerThread( workImpl ); 65 | std::thread consumerThread( aFunction ); 66 | 67 | { 68 | std::lock_guard lck(mtx); 69 | isReady = true; 70 | 71 | std::cout << "Main sending signal to producer\n"; 72 | } 73 | cv.notify_one(); 74 | 75 | producerThread.join(); 76 | consumerThread.join(); 77 | 78 | return EXIT_SUCCESS; 79 | } 80 | 81 | /* 82 | * Main sending signal to producer 83 | * Consumer thread waiting for producer to complete its task... 84 | * Producer thread is processing data 85 | * Producer thread completed! 86 | * Consumer thread knows producer is done 87 | * Consumer thread done! 88 | */ -------------------------------------------------------------------------------- /build-and-args-threads.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * PASSING ARGUMENTS TO THREADS * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /*! 10 | * From https://en.cppreference.com/w/cpp/thread/thread/thread 11 | * template< class Function, class... Args > 12 | * explicit thread( Function&& f, Args&&... args ); 13 | */ 14 | 15 | // ----- 1 : Call by reference ----- // 16 | void doTheJobByRef(int& a) { 17 | for ( int i = 0; i< 5; i++ ) 18 | { 19 | std::cout << "Doing the job by reference... " << ++a << "\n"; 20 | std::this_thread::sleep_for( std::chrono::milliseconds(5) ); 21 | } 22 | } 23 | 24 | // ----- 2 : Call by value ----- // 25 | void doTheJobByVal(int a) { 26 | for (int i = 0; i < 5; i++) 27 | { 28 | std::cout << "Doing the job by value... " << ++a << "\n"; 29 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 30 | } 31 | } 32 | 33 | // ----- 3 : Call a method of 34 | // an instance of a class. ----- // 35 | class AClass 36 | { 37 | public: 38 | void AMethod() { 39 | for (int i = 0; i < 5; i++) 40 | { 41 | std::cout << "Doing the job in AClass::AMethod()... " << i << "\n"; 42 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 43 | } 44 | } 45 | }; 46 | 47 | // ----- 4 : Call the operator() on a 48 | // callable object. ----- // 49 | class ACallableClass 50 | { 51 | public: 52 | void operator()() { 53 | for (int i = 0; i < 5; i++) 54 | { 55 | std::cout << "Doing the job in AClass()... " << i << "\n"; 56 | std::this_thread::sleep_for(std::chrono::milliseconds(3)); 57 | } 58 | } 59 | }; 60 | 61 | int main() 62 | { 63 | int var{0}; 64 | AClass myClass; 65 | ACallableClass myCallableClass; 66 | 67 | std::cout << "Before the thread : " << var << "\n"; 68 | 69 | std::thread t1( doTheJobByRef, std::ref(var) ); // Pass by reference 70 | std::thread t2( doTheJobByVal, var ); // Pass by value 71 | std::thread t3( std::move(t2) ); // t3 is now running t2 72 | std::thread t4( &AClass::AMethod, &myClass ); // Runs AClass::AMethod() on myClass 73 | std::thread t5( myCallableClass ); // Runs operator() on myCallableClass 74 | 75 | // t2 is not a thread anymore 76 | std::cout << "t2 is " << ( t2.joinable() ? "joinable\n" : "not joinable\n" ); 77 | 78 | t1.join(); 79 | t3.join(); 80 | t4.join(); 81 | t5.join(); 82 | 83 | std::cout << "After the thread : " << var << std::endl; 84 | 85 | return EXIT_SUCCESS; 86 | } 87 | 88 | /* 89 | Before the thread : 0 90 | Doing the job by reference... 1 91 | Doing the job by value... 1 92 | Doing the job in AClass::AMethod()... 0 93 | Doing the job in AClass()... 0 94 | t2 is not joinable 95 | Doing the job in AClass::AMethod()... 1 96 | Doing the job in AClass::AMethod()... 2 97 | Doing the job in AClass()... 1 98 | Doing the job in AClass::AMethod()... 3 99 | Doing the job by reference... 2 100 | Doing the job in AClass::AMethod()... 4 101 | Doing the job in AClass()... 2 102 | Doing the job in AClass()... 3 103 | Doing the job by value... 2 104 | Doing the job by reference... 3 105 | Doing the job in AClass()... 4 106 | Doing the job by reference... 4 107 | Doing the job by reference... 5 108 | Doing the job by value... 3 109 | Doing the job by value... 4 110 | Doing the job by value... 5 111 | After the thread : 5 112 | */ -------------------------------------------------------------------------------- /custom-parallel-find-1.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * PARALLEL FIND ALGORITHM IMPLEMENTATION * 3 | * USING STD::PACKAGED_TASK * 4 | ************************************************************/ 5 | 6 | /*! 7 | * @brief std::packaged_task is a class template that allows you 8 | * to wrap and asynchronously call any callable object. 9 | * You will get the callable object's return value 10 | * as a std:future. 11 | * 12 | * See https://en.cppreference.com/w/cpp/thread/packaged_task 13 | * for more details. 14 | */ 15 | 16 | /*! 17 | * @note We are using std::packaged_task to perform a custom 18 | * parallel implementation of the std::find that allows 19 | * to perform a search in a given range. 20 | * 21 | * See http://www.cplusplus.com/reference/algorithm/find/ 22 | * for more details about std::find 23 | * 24 | * Please note that C++17 provides its own implementation 25 | * of parallel algorithms that should of course be used 26 | * instead of this basic example. 27 | * 28 | * See https://en.cppreference.com/w/cpp/algorithm/find 29 | * for more details. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | ////////////////////////////////////////////////////////////////////////////////////////// 47 | /*! 48 | * @brief coutWrapper 49 | * Thread-safe cout wrapper. 50 | */ 51 | class coutWrapper : public std::stringstream { 52 | public: 53 | coutWrapper() = default; 54 | ~coutWrapper() { 55 | std::lock_guard l_lck(m_mtx); 56 | std::cout << std::stringstream::rdbuf(); 57 | std::cout.flush(); 58 | } 59 | 60 | private: 61 | static inline std::mutex m_mtx; 62 | }; 63 | 64 | ////////////////////////////////////////////////////////////////////////////////////////// 65 | /* 66 | * @brief A stopwatch class to perform measures 67 | */ 68 | template < typename Clock = std::chrono::high_resolution_clock > 69 | class stopwatch 70 | { 71 | private: 72 | const typename Clock::time_point m_start; 73 | const std::string m_title; 74 | 75 | public: 76 | stopwatch(const std::string& p_title = "") : m_title(p_title), m_start( Clock::now() ) {} 77 | ~stopwatch() { 78 | std::cout << "Computation of " << m_title << " performed in " 79 | << elapsed_time() << " ms\n"; 80 | } 81 | 82 | template < typename Rep = typename Clock::duration::rep, 83 | typename Units = typename Clock::duration > 84 | Rep elapsed_time(void) const 85 | { 86 | std::atomic_thread_fence(std::memory_order_relaxed); 87 | auto l_time = std::chrono::duration_cast(Clock::now() - m_start).count(); 88 | std::atomic_thread_fence(std::memory_order_relaxed); 89 | 90 | return static_cast(l_time); 91 | } 92 | }; 93 | 94 | using precise_stopwatch = stopwatch<>; 95 | using system_stopwatch = stopwatch; 96 | using monotonic_stopwatch = stopwatch; 97 | 98 | ////////////////////////////////////////////////////////////////////////////////////////// 99 | /*! 100 | * @brief custom_find 101 | * Custom parallel implementation of std::find 102 | * using std::thread and std::promise, std::future. 103 | */ 104 | template < class It, class T > 105 | It custom_find( It p_first, It p_last, const T& p_val ) 106 | { 107 | /*! 108 | * @brief finder 109 | * Callable struct to perform the 110 | * search for one thread. 111 | */ 112 | class finder 113 | { 114 | public: 115 | void operator()( It p_first, 116 | It p_last, 117 | const T& p_val, 118 | std::promise& p_res, 119 | std::atomic& p_done, 120 | std::mutex& p_mtx ) 121 | { 122 | coutWrapper{} << "Thread " << std::this_thread::get_id() << " - launched.\n"; 123 | try 124 | { 125 | It p_cur { p_first }; 126 | 127 | for (; ( p_cur != p_last ) && !p_done.load(); ++p_cur ) 128 | { 129 | if ( *p_cur == p_val ) 130 | { 131 | // /!\ Can be a problem if already set 132 | // so we protect it with a mutex 133 | std::lock_guard lck(p_mtx); 134 | 135 | if ( !p_done.load() ) 136 | { 137 | p_done.store ( true ); 138 | p_res .set_value( p_cur ); 139 | coutWrapper{} << "Thread " << std::this_thread::get_id() 140 | << " - found the value!\n"; 141 | } 142 | return; 143 | } 144 | } 145 | } 146 | catch ( ... ) 147 | { 148 | p_res .set_exception( std::current_exception() ); 149 | p_done.store ( true ); 150 | } 151 | } 152 | }; 153 | 154 | static const unsigned long threads_hw{ std::thread::hardware_concurrency() }; 155 | 156 | const unsigned long length = static_cast< unsigned long >( std::distance(p_first, p_last) ); 157 | if ( !length ) 158 | return p_last; 159 | 160 | const unsigned long min_per_thread { 25 }; 161 | const unsigned long max_threads { (length + min_per_thread -1) / min_per_thread }; 162 | const unsigned long threads_nb { std::min( threads_hw ? threads_hw : 2, max_threads ) }; 163 | const unsigned long block_sz { length / threads_nb }; 164 | 165 | std::atomic l_flag{false}; 166 | std::promise l_res; 167 | std::mutex l_mtx; 168 | std::vector< std::thread > l_threads(threads_nb); 169 | 170 | It block_str { p_first }; 171 | for ( unsigned long i = 0; i < threads_nb; i++ ) 172 | { 173 | It block_end { block_str }; 174 | std::advance( block_end, block_sz ); 175 | 176 | l_threads[i] = std::thread( finder(), 177 | block_str, 178 | block_end, 179 | std::ref(p_val), 180 | std::ref(l_res), 181 | std::ref(l_flag), 182 | std::ref(l_mtx) ); 183 | 184 | block_str = block_end; 185 | } 186 | 187 | for ( auto& elm : l_threads ) { elm.join(); } 188 | 189 | return l_flag.load() ? l_res.get_future().get() : p_last; 190 | } 191 | 192 | #define ELEMENTS 1e7 // The number of elements in the vector 193 | #define FIND_ELM 42 // The element to find 194 | 195 | int main() 196 | { 197 | // Generate random numbers for the vector 198 | std::uniform_int_distribution distrib(0, 10*ELEMENTS); 199 | std::default_random_engine random_engine; 200 | 201 | // Create a vector of random double elements to be sorted 202 | std::vector myVec(ELEMENTS); 203 | for ( auto& v : myVec ) { v = distrib(random_engine); } 204 | 205 | std::cout << "----- INPUT SIZE : " << ELEMENTS << " -----\n"; 206 | { 207 | stopwatch watch("CUSTOM PARALLEL_FIND"); 208 | auto resIt = custom_find( std::begin(myVec), std::end(myVec), FIND_ELM ); 209 | } 210 | 211 | { 212 | stopwatch watch("SEQUENTIAL STD::FIND"); 213 | auto resIt = std::find( std::begin(myVec), std::end(myVec), FIND_ELM ); 214 | } 215 | 216 | return EXIT_SUCCESS; 217 | } 218 | 219 | /* 220 | ----- INPUT SIZE : 1e+07 ----- 221 | Thread 2 - launched. 222 | Thread 3 - launched. 223 | Thread 4 - launched. 224 | Thread 5 - launched. 225 | Thread 4 - found the value! 226 | Thread 6 - launched. 227 | Thread 7 - launched. 228 | Thread 8 - launched. 229 | Thread 9 - launched. 230 | Computation of CUSTOM PARALLEL_FIND performed in 4 ms 231 | Computation of SEQUENTIAL STD::FIND performed in 12 ms 232 | */ -------------------------------------------------------------------------------- /multithread-single-exec.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * ENSURE SINGLE EXECUTION IN MULTITHREADED CONTEXT * 3 | ************************************************************/ 4 | 5 | /*! 6 | * @brief std::call_one allows to execute a callable object 7 | * exactly once, even in multithreaded contexts, 8 | * when called from different threads. 9 | * 10 | * See https://en.cppreference.com/w/cpp/thread/call_once 11 | * for more details. 12 | */ 13 | 14 | /*! 15 | * @note This is a C++11 feature. 16 | */ 17 | 18 | /*! 19 | * @note When to use ? 20 | * - To perform an initialisation task 21 | * that has to happen before any thread 22 | * to be able to work but should not be repeated. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /*! 33 | * @brief coutWrapper 34 | * Thread-safe wrapper to ensure std::cout 35 | * coherence in multithreaded context. 36 | */ 37 | class coutWrapper : public std::stringstream { 38 | public: 39 | coutWrapper() = default; 40 | ~coutWrapper() { 41 | std::lock_guard l_lck(m_mtx); 42 | std::cout << std::stringstream::rdbuf(); 43 | std::cout.flush(); 44 | } 45 | 46 | private: 47 | static inline std::mutex m_mtx; 48 | }; 49 | 50 | void init_once( std::once_flag& p_flag ) { 51 | // Every thread can read there 52 | coutWrapper{} << "T" << std::this_thread::get_id() << ": in init_once()\n"; 53 | 54 | // But only one will call this 55 | std::call_once( p_flag, 56 | [](){ coutWrapper{} << "Init step called only once by T" 57 | << std::this_thread::get_id() <<"\n"; } ); 58 | } 59 | 60 | void thread_work( std::once_flag& p_flag ) { 61 | coutWrapper{} << "T" << std::this_thread::get_id() << " work\n"; 62 | 63 | // Perform initialisation once 64 | init_once( p_flag ); 65 | 66 | // Do the work 67 | // ... 68 | } 69 | 70 | #define THREADS_NB 5 71 | int main() 72 | { 73 | std::once_flag flag; 74 | 75 | std::vector myThreads; 76 | for ( int i = 0; i < THREADS_NB; i++ ) 77 | { 78 | myThreads.emplace_back( std::thread(thread_work, std::ref(flag)) ); 79 | } 80 | 81 | for ( auto& t : myThreads ) { t.join(); } 82 | 83 | coutWrapper{} << "The End.\n"; 84 | 85 | return EXIT_SUCCESS; 86 | } -------------------------------------------------------------------------------- /raii-thread-wrapper.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * A RAII thread wrapper to provide safe threads 3 | * Inspired from Tom Scott lecture available here 4 | * https://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler 5 | * 6 | * Here is also a very interesting discussion on the problem 7 | * and possible solutions : 8 | * https://isocpp.org/files/papers/p0206r0.html 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class ThreadWrapper { 16 | public: 17 | typedef void (std::thread::*RAIIAction)(); 18 | 19 | explicit ThreadWrapper(std::thread&& p_thread, RAIIAction p_action) : 20 | m_thread(std::move(p_thread)), 21 | m_action(p_action) 22 | {} 23 | 24 | // No copy or assignement allowed 25 | ThreadWrapper(const ThreadWrapper& ) = delete; 26 | ThreadWrapper& operator=(const ThreadWrapper& ) = delete; 27 | 28 | // We can provide a getter for the underlying std::thread in order 29 | // to work with it. 30 | std::thread& get(void) { return m_thread; } 31 | 32 | // RAII - The destructor calls for joinable or detach 33 | // if the std::thread is joinable so that the program 34 | // is safe. 35 | ~ThreadWrapper() { if ( m_thread.joinable() ) (m_thread.*m_action)(); } 36 | 37 | private : 38 | std::thread m_thread; 39 | RAIIAction m_action; 40 | }; 41 | 42 | void doTheJob(void) { std::cout << "Doing the job...\n"; } 43 | 44 | void doTheBrokenJob(void) { 45 | std::cout << "Doing a broken job...\n"; 46 | throw std::runtime_error("A runtime error"); 47 | } 48 | 49 | #define T1 1 // perform test1 50 | #define T2 0 // perform test2 51 | #define T3 0 // perform test3 52 | 53 | int main() 54 | { 55 | if ( T1 ) 56 | { 57 | // This thread will call join on destruction 58 | // OK ! 59 | std::cout << "Test 1\n"; 60 | ThreadWrapper t( std::thread( doTheJob ), &std::thread::join ); 61 | } 62 | 63 | if ( T2 ) 64 | { 65 | // This thread will enter its destructor while 66 | // joinable, resulting in an unsafe program ( calling 67 | // std::terminate() ) 68 | std::cout << "Test 2\n"; 69 | std::thread t( doTheJob ); 70 | } 71 | 72 | if ( T3 ) 73 | { 74 | // This thread will never be able to call 75 | // joinable or detach because of the exception 76 | // in the doTheBrokenJob() function. 77 | // It will therefore also result in an unsage program. 78 | std::cout << "Test 3\n"; 79 | std::thread t( doTheBrokenJob ); 80 | t.join(); 81 | } 82 | 83 | std::cout << "Done !\n"; 84 | 85 | return EXIT_SUCCESS; 86 | } -------------------------------------------------------------------------------- /std-async-example.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * BACKGROUND TASKS USING STD::ASYNC * 3 | ************************************************************/ 4 | 5 | /*! 6 | * @brief std::async allows you to run a function 7 | * asynchronously and will return its value 8 | * is a std:future. 9 | * 10 | * See https://en.cppreference.com/w/cpp/thread/async 11 | * for more details. 12 | * 13 | * NB : This code is inspired by the book 14 | * "C++17 STL Cookbook" by Jacek Galowicz 15 | */ 16 | 17 | /*! 18 | * @note You can decide the launch policy 19 | * very easily : 20 | * - std::launch::async guarantees the function 21 | * to be executed by another thread. 22 | * - std::launch::deferred will execute the function 23 | * in the same thread but later (lazy evaluation). 24 | * The execution happens when get() or wait() is 25 | * called on the std::future so if none of both 26 | * happens, the function will not be called at all ! 27 | * - std::launch::async | std::launch::deferred (default) 28 | * The STL's std::async is free to choose which policy 29 | * shall be followed. 30 | */ 31 | 32 | /*! 33 | * @note Exceptions 34 | * std::async can throw std::system_error with error 35 | * condition std::errc::resource_unavailable_try_again 36 | * if the launch policy is std::async but the impl was 37 | * unable to launch a thread. 38 | */ 39 | 40 | /*! 41 | * @note When to use ? 42 | * - To perform an tasks in background when it is not 43 | * convenient to launch std::thread and join() them. 44 | * For example, when you need to access the task 45 | * return value. 46 | */ 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | /*! 57 | * @brief flip_map 58 | * Helper function for task1. 59 | */ 60 | template 61 | std::multimap flip_map(const std::map &src) 62 | { 63 | std::multimap l_ret; 64 | std::transform(src.begin(), src.end(), std::inserter(l_ret, l_ret.begin()), 65 | [](const auto &p) { return std::make_pair(p.second, p.first); }); 66 | return l_ret; 67 | } 68 | 69 | /*! 70 | * @brief task1 71 | * compute a sorted histogram of characters 72 | * in a given std::string 73 | */ 74 | std::multimap task1(const std::string &p_input) 75 | { 76 | std::map l_ret; 77 | 78 | for ( const char& c : p_input ) { ++l_ret[std::tolower(c)]; } 79 | return flip_map(l_ret); 80 | } 81 | 82 | /*! 83 | * @brief task2 84 | * Compute a sorted copy of an input string. 85 | */ 86 | std::string task2( const std::string& p_input ) 87 | { 88 | std::string l_ret; 89 | std::transform( std::begin(p_input), 90 | std::end (p_input), 91 | std::back_inserter(l_ret), 92 | [](const char& c){ return std::tolower(c); }); 93 | 94 | std::sort( std::begin(l_ret), std::end(l_ret) ); 95 | return l_ret; 96 | } 97 | 98 | size_t task3( const std::string& p_input ) 99 | { 100 | static char vowels[] {"aeiouy"}; 101 | return std::count_if( std::begin(p_input), 102 | std::end (p_input), 103 | [](const char& c){ 104 | return std::find( std::begin(vowels), 105 | std::end (vowels), 106 | std::tolower(c) ) != std::end(vowels); } ); 107 | } 108 | 109 | int main() 110 | { 111 | std::string inputStr { "Hello beautiful World! Nice to meet you!" }; 112 | auto mySortedHist ( std::async( std::launch::async, 113 | task1, 114 | inputStr ) ); // std::future> 115 | auto mySortedStr ( std::async( std::launch::async, 116 | task2, 117 | inputStr ) ); // std::future 118 | auto myVowelsNb ( std::async( std::launch::deferred, 119 | task3, 120 | inputStr ) ); // std::future 121 | /* 122 | * Do whatever you want as the tasks will perform 123 | * in the background and get the results when you need them. 124 | */ 125 | 126 | std::cout << "Sorted histogram\n"; 127 | for (const auto &[c, nb] : mySortedHist.get() ) 128 | { 129 | std::cout << c << " - " << nb << "\n"; 130 | } 131 | 132 | std::cout << "Sorted string\n" << mySortedStr.get() << "\n"; 133 | std::cout << "Number of vowels: " << myVowelsNb .get() << "\n"; 134 | 135 | return EXIT_SUCCESS; 136 | } -------------------------------------------------------------------------------- /std-packaged-task-basics.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * BASICS OF STD::PACKAGED_TASK * 3 | ************************************************************/ 4 | 5 | /*! 6 | * @brief std::packaged_task is a class template that allows you 7 | * to wrap and asynchronously call any callable object. 8 | * You will get the callable object's return value 9 | * as a std:future. 10 | * 11 | * See https://en.cppreference.com/w/cpp/thread/packaged_task 12 | * for more details. 13 | */ 14 | 15 | /*! 16 | * @note When to use ? 17 | * To perform an tasks in background. 18 | * When you want to be able to : 19 | * - Invoke the start of the task (contrary to std::async) 20 | * - Move it to a std::thread (same) 21 | * 22 | * In a nutshell, std::packaged_task is a std::function linked to a 23 | * std::future and std::async wraps and calls a std::packaged_task 24 | * (possibly in a different thread). 25 | * 26 | * More resources here : 27 | * - https://stackoverflow.com/questions/18143661/what-is-the-difference-between-packaged-task-and-async 28 | * - http://scottmeyers.blogspot.com/2013/03/stdfutures-from-stdasync-arent-special.html 29 | * - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3451.pdf 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | /*! 43 | * @brief flip_map 44 | * Helper function for task1. 45 | */ 46 | template 47 | std::multimap flip_map(const std::map &src) 48 | { 49 | std::multimap l_ret; 50 | std::transform(src.begin(), src.end(), std::inserter(l_ret, l_ret.begin()), 51 | [](const auto &p) { return std::make_pair(p.second, p.first); }); 52 | return l_ret; 53 | } 54 | 55 | /*! 56 | * @brief task1 57 | * compute a sorted histogram of characters 58 | * in a given std::string 59 | */ 60 | std::multimap task1(const std::string &p_input) 61 | { 62 | std::map l_ret; 63 | 64 | for ( const char& c : p_input ) { ++l_ret[std::tolower(c)]; } 65 | return flip_map(l_ret); 66 | } 67 | 68 | /*! 69 | * @brief task2 70 | * Compute a sorted copy of an input string. 71 | */ 72 | std::string task2( const std::string& p_input ) 73 | { 74 | std::string l_ret; 75 | std::transform( std::begin(p_input), 76 | std::end (p_input), 77 | std::back_inserter(l_ret), 78 | [](const char& c){ return std::tolower(c); }); 79 | 80 | std::sort( std::begin(l_ret), std::end(l_ret) ); 81 | return l_ret; 82 | } 83 | 84 | size_t task3( const std::string& p_input ) 85 | { 86 | static char vowels[] {"aeiouy"}; 87 | return std::count_if( std::begin(p_input), 88 | std::end (p_input), 89 | [](const char& c){ 90 | return std::find( std::begin(vowels), 91 | std::end (vowels), 92 | std::tolower(c) ) != std::end(vowels); } ); 93 | } 94 | 95 | int main() 96 | { 97 | std::string inputStr { "Hello beautiful World! Nice to meet you!" }; 98 | 99 | // You can bind the callable object (in that case a function) 100 | // to its agruments 101 | std::packaged_task< std::multimap( const std::string& ) > 102 | ptask1 ( std::bind(task1, std::ref(inputStr)) ); 103 | 104 | // Or just provide the callable object 105 | // and give the arguments at invoke call. 106 | std::packaged_task 107 | ptask2 ( task2 ); 108 | std::packaged_task< size_t( const std::string& ) > 109 | ptask3 ( task3 ); 110 | // Note : we could also use a lambda 111 | 112 | /* 113 | * Use std::packaged_task::get_future 114 | * to get the std::future that will get you the 115 | * return value when ready. 116 | * 117 | * See https://en.cppreference.com/w/cpp/thread/packaged_task/get_future 118 | * for more informations. 119 | */ 120 | auto mySortedHist = ptask1.get_future(); // std::future> 121 | auto mySortedStr = ptask2.get_future(); // std::future 122 | auto myVowelsNb = ptask3.get_future(); // std::future 123 | 124 | /* 125 | * std::packaged_task::get_future 126 | * will throw std::future_error : 127 | * - Of category std::future_already_retrived 128 | * if the shared state has already been retrieved by a call 129 | * to std::packaged_task::get_future. 130 | * - Of category std::no_state if *this has no shared state. 131 | */ 132 | try 133 | { 134 | auto tmp = ptask1.get_future(); 135 | } 136 | catch( const std::future_error& e ) 137 | { 138 | std::cerr << "Could not call get_future() on ptask1 - " 139 | << e.what() << "\n"; 140 | } 141 | 142 | 143 | /* 144 | * You need to start the tasks yourself 145 | */ 146 | ptask1( "" ); 147 | ptask2( inputStr ); 148 | ptask3( inputStr ); 149 | 150 | /* 151 | * If you call operator() twice, it will throw a 152 | * std::future_error (error category is std::promise_already_satisfied) 153 | * 154 | * See https://en.cppreference.com/w/cpp/thread/packaged_task/operator() 155 | * for more informations. 156 | */ 157 | try { 158 | // Try to invoke ptask1 again 159 | ptask1( inputStr ); 160 | } 161 | catch ( const std::future_error& e) 162 | { 163 | std::cout << "Could not invoke ptask1 - " << e.what() << "\n"; 164 | } 165 | 166 | std::cout << "Sorted histogram\n"; 167 | for (const auto &[c, nb] : mySortedHist.get() ) 168 | { 169 | std::cout << c << " - " << nb << "\n"; 170 | } 171 | 172 | std::cout << "Sorted string\n" << mySortedStr.get() << "\n"; 173 | std::cout << "Number of vowels: " << myVowelsNb .get() << "\n"; 174 | 175 | return EXIT_SUCCESS; 176 | } -------------------------------------------------------------------------------- /stl-algorithms-policies.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * @RESSOURCES 3 | * 4 | * A very interesting article from Bartłomiej Filipek 5 | * with discussion on gains and benchmark for policies 6 | * STL algorithms 7 | * -> https://www.bfilipek.com/2018/11/parallel-alg-perf.html 8 | * 9 | * The book "C++17 in detail" by Bartlomiej Filipek. 10 | * 11 | * A benchmark with working visual studio 2019 solutions 12 | * -> https://github.com/MericLuc/ParallelAlgorithms 13 | */ 14 | 15 | /*! 16 | * @NOTES 17 | * 18 | * Not every compilers support the "Standardization of Parallelism TS" 19 | * feature. 20 | * At mentioned in https://en.cppreference.com/w/cpp/compiler_support 21 | * here are the only compiliers that may run this program at 22 | * the moment : 23 | * - GCC libstdc++ 24 | * - MSVC 25 | * - Intel C++ 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | // ------------------------ Utility ------------------------ // 37 | 38 | /* 39 | * @brief A stopwatch class to perform measures 40 | */ 41 | template < typename Clock = std::chrono::high_resolution_clock > 42 | class stopwatch 43 | { 44 | private: 45 | const typename Clock::time_point m_start; 46 | 47 | public: 48 | stopwatch() : m_start( Clock::now() ) {} 49 | ~stopwatch() { 50 | std::cout << "Computation performed in " 51 | << elapsed_time() << " ms\n"; 52 | } 53 | 54 | template < typename Rep = typename Clock::duration::rep, 55 | typename Units = typename Clock::duration > 56 | Rep elapsed_time(void) const 57 | { 58 | std::atomic_thread_fence(std::memory_order_relaxed); 59 | auto l_time = std::chrono::duration_cast(Clock::now() - m_start).count(); 60 | std::atomic_thread_fence(std::memory_order_relaxed); 61 | 62 | return static_cast(l_time); 63 | } 64 | }; 65 | 66 | using precise_stopwatch = stopwatch<>; 67 | using system_stopwatch = stopwatch; 68 | using monotonic_stopwatch = stopwatch; 69 | 70 | // ------------------------ Main ------------------------ // 71 | 72 | #define ELEMENTS 1e6 // The number of elements in the vector 73 | int main() 74 | { 75 | // Generate random numbers for the vector 76 | std::uniform_real_distribution distrib(0, 100); 77 | std::default_random_engine random_engine; 78 | 79 | // Create a vector of random double elements to be sorted 80 | std::vector myVec(ELEMENTS); 81 | for ( auto& v : myVec ) { v = distrib(random_engine); } 82 | 83 | { 84 | // ----- Test 1 ----- // 85 | // - sequential sort 86 | std::cout << "Test 1 - Sequential sort over " << ELEMENTS << " elements\n"; 87 | stopwatch watch; 88 | std::sort( std::execution::seq, std::begin(myVec), std::end(myVec)); 89 | } 90 | 91 | { 92 | // ----- Test 2 ----- // 93 | // - parallel sort 94 | std::cout << "Test 1 - Parallel sort over " << ELEMENTS << " elements\n"; 95 | stopwatch watch; 96 | std::sort(std::execution::par, std::begin(myVec), std::end(myVec)); 97 | } 98 | 99 | { 100 | // ----- Test 3 ----- // 101 | // - parallel and vectorized sort 102 | std::cout << "Test 1 - Parallel and vectorized sort over " << ELEMENTS << " elements\n"; 103 | stopwatch watch; 104 | std::sort(std::execution::par_unseq, std::begin(myVec), std::end(myVec)); 105 | } 106 | 107 | return EXIT_SUCCESS; 108 | } -------------------------------------------------------------------------------- /thread-pool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(main VERSION 0.1.0) 3 | 4 | include(CTest) 5 | enable_testing() 6 | 7 | message("Building ${PROJECT_NAME} project using C++17") 8 | 9 | # C++ options 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_CXX_FLAGS "-std=c++17 -O3 -g0") 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | 15 | # Executable directory 16 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 17 | message("\t${PROJECT_NAME}.exe written at ${CMAKE_CURRENT_SOURCE_DIR}") 18 | 19 | set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) 20 | set(INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/inc) 21 | 22 | include_directories(${INC_DIR}) 23 | 24 | add_executable(${PROJECT_NAME} main.cpp ${SRC_DIR}/threadpool.cpp) 25 | 26 | set(CPACK_PROJECT_NAME ${PROJECT_NAME}) 27 | set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) 28 | 29 | include(CPack) 30 | 31 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 32 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.exe 33 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 34 | COMMENT "Testing the output of the ${PROJECT_NAME} project" 35 | ) -------------------------------------------------------------------------------- /thread-pool/inc/threadpool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include //condition_variable 4 | #include //packaged_task 5 | #include //unique_lock 6 | #include //queue 7 | #include //thread 8 | #include //invoke_result, enable_if, is_invocable 9 | #include //vector 10 | 11 | class thread_pool { 12 | public: 13 | thread_pool( size_t thread_count = std::thread::hardware_concurrency() ); 14 | ~thread_pool(); 15 | 16 | // since std::thread objects are not copiable, it doesn't make sense for a 17 | // thread_pool to be copiable. 18 | thread_pool(const thread_pool& ) = delete; 19 | thread_pool &operator=(const thread_pool& ) = delete; 20 | 21 | template , int> = 0> 23 | auto execute(F &&, Args &&...); 24 | 25 | private: 26 | //_task_container_base and _task_container exist simply as a wrapper around a 27 | // MoveConstructible - but not CopyConstructible - Callable object. Since an 28 | // std::function requires a given Callable to be CopyConstructible, we 29 | // cannot construct one from a lambda function that captures a 30 | // non-CopyConstructible object (such as the packaged_task declared in 31 | // execute) - because a lambda capturing a non-CopyConstructible object is 32 | // not CopyConstructible. 33 | 34 | //_task_container_base exists only to serve as an abstract base for 35 | // _task_container. 36 | class _task_container_base { 37 | public: 38 | virtual ~_task_container_base(){}; 39 | 40 | virtual void operator()() = 0; 41 | }; 42 | using _task_ptr = std::unique_ptr<_task_container_base>; 43 | 44 | //_task_container takes a typename F, which must be Callable and 45 | // MoveConstructible. 46 | // Furthermore, F must be callable with no arguments; it can, for example, 47 | // be a bind object with no placeholders. F may or may not be 48 | // CopyConstructible. 49 | template , int> = 0> 50 | class _task_container : public _task_container_base { 51 | public: 52 | // here, std::forward is needed because we need the construction of _f not 53 | // to bind an lvalue reference - it is not a guarantee that an object of 54 | // type F is CopyConstructible, only that it is MoveConstructible. 55 | _task_container(F &&func) : _f( std::forward(func) ) {} 56 | 57 | void operator()() override { _f(); } 58 | 59 | private: 60 | F _f; 61 | }; 62 | 63 | std::vector _threads; 64 | std::queue<_task_ptr> _tasks; 65 | std::mutex _task_mutex; 66 | std::condition_variable _task_cv; 67 | bool _stop_threads{false}; 68 | }; 69 | 70 | template , int>> 72 | auto thread_pool::execute(F &&function, Args &&...args) 73 | { 74 | std::unique_lock queue_lock(_task_mutex, std::defer_lock); 75 | std::packaged_task()> task_pkg( 76 | [_f = std::move(function), 77 | _fargs = std::make_tuple(std::forward(args)...)]() mutable { 78 | return std::apply(std::move(_f), std::move(_fargs)); 79 | }); 80 | std::future> future = task_pkg.get_future(); 81 | 82 | queue_lock.lock(); 83 | // this lambda move-captures the packaged_task declared above. Since the 84 | // packaged_task type is not CopyConstructible, the function is not 85 | // CopyConstructible either - hence the need for a _task_container to wrap 86 | // around it. 87 | _tasks.emplace(_task_ptr( 88 | new _task_container([task(std::move(task_pkg))]() mutable { task(); }))); 89 | 90 | queue_lock.unlock(); 91 | 92 | _task_cv.notify_one(); 93 | 94 | return std::move(future); 95 | } 96 | -------------------------------------------------------------------------------- /thread-pool/main.cpp: -------------------------------------------------------------------------------- 1 | // Implementation of the thread_pool class by osuka_ 2 | // https://codereview.stackexchange.com/questions/221626/c17-thread-pool 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int multiply(int x, int y) 9 | { 10 | return x * y; 11 | } 12 | 13 | int main() 14 | { 15 | thread_pool pool ; 16 | std::vector> futures; 17 | 18 | for ( const int &x : {2, 4, 7, 13} ) 19 | { 20 | futures.push_back( pool.execute(multiply, x, 2) ); 21 | } 22 | 23 | for (auto &fut : futures) 24 | { 25 | std::cout << fut.get() << std::endl; 26 | } 27 | 28 | return 0; 29 | } -------------------------------------------------------------------------------- /thread-pool/src/threadpool.cpp: -------------------------------------------------------------------------------- 1 | #include "threadpool.h" 2 | 3 | thread_pool::thread_pool(size_t thread_count) { 4 | for (size_t i = 0; i < thread_count; ++i) { 5 | // start waiting threads. Workers listen for changes through 6 | // the thread_pool member condition_variable 7 | _threads.emplace_back(std::thread([&]() { 8 | std::unique_lock queue_lock(_task_mutex, std::defer_lock); 9 | 10 | while (true) { 11 | queue_lock.lock(); 12 | _task_cv.wait(queue_lock, [&]() -> bool { 13 | return !_tasks.empty() || _stop_threads; 14 | }); 15 | 16 | // used by dtor to stop all threads without having to 17 | // unceremoniously stop tasks. The tasks must all be 18 | // finished, lest we break a promise and risk a `future` 19 | // object throwing an exception. 20 | if (_stop_threads && _tasks.empty()) 21 | return; 22 | 23 | // to initialize temp_task, we must move the unique_ptr 24 | // from the queue to the local stack. Since a unique_ptr 25 | // cannot be copied (obviously), it must be explicitly 26 | // moved. This transfers ownership of the pointed-to 27 | // object to *this, as specified in 20.11.1.2.1 28 | // [unique.ptr.single.ctor]. 29 | auto temp_task = std::move(_tasks.front()); 30 | 31 | _tasks.pop(); 32 | queue_lock.unlock(); 33 | 34 | (*temp_task)(); 35 | } 36 | })); 37 | } 38 | } 39 | 40 | thread_pool::~thread_pool() { 41 | _stop_threads = true; 42 | _task_cv.notify_all(); 43 | 44 | for (std::thread &thread : _threads) { 45 | thread.join(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /thread-safe-cout-wrapper.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * THREAD SAFE STD::COUT WRAPPER * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /*! 13 | * @note C++20 introduces a built-in class to 14 | * perform synchronised std::cout wrapper 15 | * with std::osyncstream. 16 | * 17 | * See http://en.cppreference.com/w/cpp/io/basic_osyncstream 18 | * for more details. 19 | */ 20 | 21 | /*! 22 | * @note Our wrapper object only lives for one line. 23 | * The destructor is responsible for printing 24 | * the underlying buffer content. 25 | */ 26 | 27 | /*! 28 | * @note coutWrapper is still a std::stringstream 29 | * and can of course be used like one. 30 | */ 31 | 32 | class coutWrapper : public std::stringstream { 33 | public: 34 | coutWrapper() = default; 35 | ~coutWrapper() { 36 | std::lock_guard l_lck(m_mtx); 37 | std::cout << std::stringstream::rdbuf(); 38 | std::cout.flush(); 39 | } 40 | 41 | private: 42 | static inline std::mutex m_mtx; 43 | }; 44 | 45 | void thread_function(int id) { 46 | // Do the work 47 | // ... 48 | 49 | // Use std::cout to see the mess 50 | std::cout << "(cout) In the thread " << id << "\n"; 51 | // Then call our wrapper 52 | coutWrapper{} << "(wrap) In the thread " << id << "\n"; 53 | } 54 | 55 | int main() 56 | { 57 | std::vector myThreads; 58 | for ( int i = 0; i < 5; i++ ) 59 | { 60 | myThreads.emplace_back( thread_function, i ); 61 | } 62 | 63 | for ( auto& t : myThreads ) { t.join(); } 64 | 65 | coutWrapper{} << "It worked fine\n"; 66 | 67 | return EXIT_SUCCESS; 68 | } -------------------------------------------------------------------------------- /thread_safe_queue_example.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * THREAD SAFE QUEUE * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /*! 16 | * @brief queueThreadSafe 17 | * push() and pop() operations can only be performed if 18 | * the queue state is OPENED. 19 | * These operations also have a settable timeout and will 20 | * give an indication of success/failure: 21 | * - through a bool for push() 22 | * - through std::optional for pop() 23 | */ 24 | 25 | /*! 26 | * @note We will make use of some C++17 features 27 | * such as std::variant for error-handling. 28 | * 29 | * You can find more informations about std::variant 30 | * here https://en.cppreference.com/w/cpp/utility/variant 31 | * and some tests on it here 32 | * https://github.com/MericLuc/Cpp17-Features-tests/std-variant 33 | */ 34 | 35 | template 36 | class queueThreadSafe 37 | { 38 | public: 39 | enum State { OPENED, CLOSED }; 40 | 41 | enum class StatusCode { 42 | ERR_NO , // No error 43 | ERR_FULL , // Capacity error - trying to push() on a queue that is full. 44 | ERR_EMPTY , // Capacity error - trying to get() on a queue that is empty. 45 | ERR_TIMOUT, // Timeout error - trying to get() or push() but timed out. 46 | ERR_ACCESS // Access error - trying to get() or push() on CLOSED queue. 47 | }; 48 | 49 | explicit queueThreadSafe(size_t p_cap = 0) : m_state(OPENED), m_size(0), m_cap(p_cap) {} 50 | ~queueThreadSafe() { close(); } 51 | 52 | queueThreadSafe(const& queueThreadSafe) = delete; 53 | queueThreadSafe& operator=(const& queueThreadSafe) = delete; 54 | 55 | static std::string getStatus( StatusCode&& p_code ) { 56 | return m_statusStr.at(p_code); 57 | } 58 | 59 | static std::string getStatus( const std::variant& p_code ) { 60 | return ( std::holds_alternative(p_code) ) ? 61 | m_statusStr.at(std::get(p_code)) : 62 | m_statusStr.at(StatusCode::ERR_NO); 63 | } 64 | 65 | void close() 66 | { 67 | std::unique_lock lck(m_mtx); 68 | m_state = State::CLOSED; 69 | 70 | m_push.notify_all(); 71 | m_pop .notify_all(); 72 | } 73 | 74 | /* 75 | * @brief push 76 | * Will block in case of full queue untill timeout 77 | * or space appears. 78 | */ 79 | [[maybe_unused]] StatusCode push( const T & p_elm, 80 | uint32_t&& p_ms = 2000 ) 81 | { 82 | std::unique_lock lck(m_mtx); 83 | 84 | // Wait untill "There is some place" OR "timeout" 85 | m_pop.wait_for(lck, 86 | std::chrono::milliseconds(p_ms), 87 | [this] { return (m_size < m_cap && m_state == State::OPENED ); }); 88 | 89 | if ( m_size == m_cap ) 90 | return StatusCode::ERR_FULL; 91 | 92 | if ( m_state == State::CLOSED ) 93 | return StatusCode::ERR_ACCESS; 94 | 95 | ++m_size; 96 | m_data.push_back (p_elm ); 97 | m_pop.notify_one(); 98 | 99 | return StatusCode::ERR_NO; 100 | } 101 | 102 | [[maybe_unused]] StatusCode push(T &&p_elm, 103 | uint32_t &&p_ms = 2000 ) 104 | { 105 | std::unique_lock lck(m_mtx); 106 | 107 | // Wait untill "There is some place" OR "timeout" 108 | m_pop.wait_for(lck, 109 | std::chrono::milliseconds(p_ms), 110 | [this] { return (m_size < m_cap && m_state == State::OPENED); }); 111 | 112 | if (m_size == m_cap) 113 | return StatusCode::ERR_FULL; 114 | 115 | if (m_state == State::CLOSED) 116 | return StatusCode::ERR_ACCESS; 117 | 118 | ++m_size; 119 | m_data.push_back( p_elm ); 120 | m_pop.notify_one(); 121 | 122 | return StatusCode::ERR_NO; 123 | } 124 | 125 | /*! 126 | * @brief pop 127 | * Will return a std::variant that contains 128 | * a value if possible, otherwise the corresponding StatusCode. 129 | */ 130 | std::variant pop( std::chrono::milliseconds &&p_ms = std::chrono::milliseconds(1) ) 131 | { 132 | std::unique_lock lck(m_mtx); 133 | 134 | // Wait untill "There is one item" OR "timeout" 135 | m_pop.wait_for(lck, 136 | p_ms, 137 | [this]{ return !m_data.empty() && m_state == State::OPENED; }); 138 | if ( m_data.empty() ) 139 | return StatusCode::ERR_EMPTY; 140 | 141 | if (m_state == State::CLOSED) 142 | return StatusCode::ERR_ACCESS; 143 | 144 | --m_size; 145 | T l_ret = m_data.front(); 146 | m_data.pop_front(); 147 | m_push.notify_one(); 148 | 149 | return l_ret; 150 | } 151 | 152 | private: 153 | State m_state; /*!< State of the queue */ 154 | size_t m_size; /*!< Current size of the queue */ 155 | size_t m_cap; /*!< Capacity of the queue */ 156 | std::mutex m_mtx; /*!< Mutex for operations */ 157 | std::list m_data; /*!< Underlying container */ 158 | std::condition_variable m_push; /*!< Condition variable for producers */ 159 | std::condition_variable m_pop ; /*!< Condition variable for consumers */ 160 | 161 | inline static const std::map m_statusStr = 162 | { 163 | { StatusCode::ERR_NO , "OK!\n"}, 164 | { StatusCode::ERR_FULL , "Queue is full\n"}, 165 | { StatusCode::ERR_EMPTY , "Queue is empty\n"}, 166 | { StatusCode::ERR_TIMOUT , "Timed out before end of operation\n"}, 167 | { StatusCode::ERR_ACCESS , "Trying to access closed queue\n"} 168 | }; 169 | }; 170 | 171 | int main() 172 | { 173 | const uint32_t THREADS_NB { 5}; 174 | const uint32_t OPERATIONS_NB { 4}; 175 | const size_t QUEUE_CAPACITY{10}; 176 | 177 | std::mutex iomutex; 178 | std::vector producers, consumers; 179 | 180 | queueThreadSafe myQueue(QUEUE_CAPACITY); 181 | 182 | // THREADS_NB Producers threads doing 183 | // OPERATIONS_NB operations on the queue. 184 | for ( uint32_t id = 0; id < THREADS_NB; ++id ) 185 | { 186 | producers.push_back(std::thread([&, id] { 187 | for ( uint32_t i = 0; i < OPERATIONS_NB; ++i ) 188 | { 189 | { 190 | std::lock_guard lck(iomutex); 191 | std::cout << "T" << id << ": trying to push " << (id * THREADS_NB + i) << "..."; 192 | 193 | std::cout << queueThreadSafe::getStatus(myQueue.push(id * THREADS_NB + i)); 194 | } 195 | } 196 | })); 197 | } 198 | 199 | // THREADS_NB Producers threads doing 200 | // OPERATIONS_NB operations on the queue. 201 | for ( uint32_t id = THREADS_NB; id < 2 * THREADS_NB; ++id ) 202 | { 203 | consumers.push_back(std::thread([&, id] { 204 | auto maybe = myQueue.pop( std::chrono::milliseconds(5) ); 205 | { 206 | std::lock_guard lck(iomutex); 207 | std::cerr << "T" << id << ": poped - " << queueThreadSafe::getStatus(maybe); 208 | } 209 | })); 210 | } 211 | 212 | for (auto &t : producers) t.join(); 213 | for (auto &t : consumers) t.join(); 214 | 215 | myQueue.close(); 216 | 217 | std::cout << "ok\n"; 218 | 219 | return EXIT_SUCCESS; 220 | } 221 | -------------------------------------------------------------------------------- /transfer-ownership-threads.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * TRANSFER OWNERSHIP OF THREADS * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /*! 10 | * From https://en.cppreference.com/w/cpp/thread/thread/thread 11 | * 12 | * Creates a new thread object which is not a thread 13 | * - thread() noexcept; 14 | * No copy-constructor 15 | * - thread( const thread& ) = delete; 16 | * Move constructor 17 | * - thread( thread&& other ) noexcept; 18 | * This is the one we are interested in to transfer 19 | * ownership of a std::thread. 20 | */ 21 | 22 | void doTheJob(int a) { 23 | for ( int i = 0; i< 5; i++ ) 24 | { 25 | std::cout << std::this_thread::get_id() << " is doing the job... " << ++a << "\n"; 26 | std::this_thread::sleep_for( std::chrono::milliseconds(5) ); 27 | } 28 | } 29 | 30 | int main() 31 | { 32 | int var{0}; 33 | 34 | std::thread t1( doTheJob, var ); 35 | std::cout << "t1 id is " << t1.get_id() << "\n"; 36 | 37 | std::thread t2( std::move(t1) ); // t2 is now running t1 38 | std::cout << "t2 id is " << t2.get_id() << " and t1 id is " << t1.get_id() << "\n"; 39 | 40 | // t1 is not a thread anymore 41 | std::cout << "t1 is " << ( t1.joinable() ? "joinable\n" : "not joinable\n" ); 42 | 43 | // Assign a new thread to t1 44 | // with implicit std::move call 45 | t1 = std::thread( doTheJob, var ); 46 | 47 | std::cout << "t1 id is " << t1.get_id() << "\n"; 48 | 49 | t1.join(); 50 | t2.join(); 51 | 52 | return EXIT_SUCCESS; 53 | } -------------------------------------------------------------------------------- /useful-operations-threads.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * SOME USEFUL FUNCTIONS ON THREADS * 3 | ************************************************************/ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /*! 10 | * More infos on https://en.cppreference.com/w/cpp/thread/thread/thread 11 | */ 12 | 13 | void doTheJob() { 14 | std::cout << "Thread " << std::this_thread::get_id() << " is doing the job...\n"; 15 | } 16 | 17 | void sleepForExample() { 18 | static uint32_t sleepTime{2000}; 19 | 20 | std::cout << "The current thread is gonna sleep for " 21 | << sleepTime << "ms..."; 22 | 23 | std::this_thread::sleep_for( std::chrono::milliseconds(sleepTime) ); 24 | 25 | std::cout << "Done !\n"; 26 | } 27 | 28 | // "busy sleep" while suggesting that other threads run 29 | // for a small amount of time 30 | void little_sleep(std::chrono::microseconds us) 31 | { 32 | auto start = std::chrono::high_resolution_clock::now(); 33 | auto end = start + us; 34 | do 35 | { 36 | std::this_thread::yield(); 37 | } while ( std::chrono::high_resolution_clock::now() < end ); 38 | } 39 | 40 | int main() 41 | { 42 | /*! 43 | * --------------------------------------------- 44 | * Get the ID of a thread 45 | * --------------------------------------------- 46 | * As a public member function 47 | * std::thread::id get_id() const noexcept; 48 | * As a function 49 | * std::thread::id get_id() noexcept; 50 | */ 51 | std::thread t1; 52 | std::cout << "t1 is not a thread so its id is " << t1.get_id() << "\n"; 53 | 54 | std::thread t2( doTheJob ); 55 | std::cout << "t2 id is " << t2.get_id() << "\n"; 56 | t2.join(); 57 | 58 | /*! 59 | * --------------------------------------------- 60 | * Get the number of concurrent threads 61 | * supported by th implementation 62 | * --------------------------------------------- 63 | * 64 | * static unsigned int hardware_concurrency() noexcept; 65 | */ 66 | std::cout << std::thread::hardware_concurrency() 67 | << " concurrent threads are supported.\n"; 68 | 69 | /*! 70 | * --------------------------------------------- 71 | * Reschedule the execution of a thread, 72 | * allowing other threads to run. 73 | * --------------------------------------------- 74 | * 75 | * void yield() noexcept; 76 | */ 77 | auto start = std::chrono::high_resolution_clock::now(); 78 | 79 | little_sleep(std::chrono::microseconds(100)); 80 | 81 | auto elapsed = std::chrono::high_resolution_clock::now() - start; 82 | std::cout << "waited for " 83 | << std::chrono::duration_cast(elapsed).count() 84 | << " microseconds\n"; 85 | 86 | /*! 87 | * --------------------------------------------- 88 | * Block the execution of a thread for at least 89 | * the specified amount of time specified. 90 | * --------------------------------------------- 91 | * 92 | * template< class Rep, class Period > 93 | * void sleep_for( const std::chrono::duration& sleep_duration ); 94 | */ 95 | t1 = std::thread(sleepForExample); 96 | t1.join(); 97 | 98 | return EXIT_SUCCESS; 99 | } --------------------------------------------------------------------------------