YouTip LogoYouTip

Cpp Multithreading

A thread is a lightweight unit of execution within a program, allowing the program to perform multiple tasks simultaneously. Multithreading is a special form of multitasking. Multitasking allows a computer to run two or more programs concurrently. Generally, there are two types of multitasking: **process-based and thread-based**. * Process-based multitasking is the concurrent execution of programs. * Thread-based multitasking is the concurrent execution of different parts of the same program. !(#) C++ multithreading involves creating and managing multiple threads that execute concurrently within a single program. C++ provides robust support for multithreading, especially since the C++11 standard and later, making multithreading programming simpler and safer through the `` standard library. * * * ## Concept Explanation ### Thread * A thread is a single sequential flow of control within a program. Multiple threads can run independently within the same process. * Threads share the process's address space, file descriptors, heap, and global variables, but each thread has its own stack, registers, and program counter. ### Concurrency vs. Parallelism * **Concurrency**: Multiple tasks are executed in an interleaved manner within time slices, giving the appearance of simultaneous execution. * **Parallelism**: Multiple tasks are executed simultaneously on multiple processors or processor cores. The C++11 standard and later provide multithreading support. Core components include: * **`std::thread`**: Used to create and manage threads. * **`std::mutex`**: Used for mutual exclusion between threads, preventing multiple threads from accessing shared resources simultaneously. * **`std::lock_guard`** and **`std::unique_lock`**: Used to manage the acquisition and release of locks. * **`std::condition_variable`**: Used for condition variables between threads, coordinating waiting and notification. * **`std::future`** and **`std::promise`**: Used to implement value passing and task synchronization between threads. * * * ## Creating Threads C++ 11 introduced the new standard thread library `std::thread`. `std::thread` is declared in the `` header file, so you need to include the `` header to use `std::thread`. **Note:** Some compilers previously used the C++ 11 compilation flag **-std=c++11**: g++ -std=c++11 test.cpp std::thread #include std::thread thread_object(callable, args...); * **`callable`**: A callable object, which can be a function pointer, function object, Lambda expression, etc. * **`args...`**: The argument list passed to the `callable`. ### Using Function Pointers Creating a thread using a function pointer is the most basic method: ## Example #include #include void printMessage(int count){ for(int i =0; i < count;++i){ std::cout<<"Hello from thread (function pointer)!n"; } } int main(){ std::thread t1(printMessage, 5);// Create thread, pass function pointer and arguments t1.join();// Wait for thread to complete return 0; } After compiling with g++ -std=c++11, the execution output is: Hello from thread (function pointer)!Hello from thread (function pointer)!Hello from thread (function pointer)!Hello from thread (function pointer)!Hello from thread (function pointer)! ### Using Function Objects Creating a thread using a function object defined by the `operator()` method in a class: ## Example #include #include class PrintTask { public: void operator()(int count)const{ for(int i =0; i < count;++i){ std::cout<<"Hello from thread (function object)!n"; } } }; int main(){ std::thread t2(PrintTask(), 5);// Create thread, pass function object and arguments t2.join();// Wait for thread to complete return 0; } After compiling with g++ -std=c++11, the execution output is: Hello from thread (function object)!Hello from thread (function object)!Hello from thread (function object)!Hello from thread (function object)!Hello from thread (function object)! ### Using Lambda Expressions Lambda expressions can be used to define the code to be executed by the thread inline: ## Example #include #include int main(){ std::thread t3([](int count){ for(int i =0; i < count;++i){ std::cout<<"Hello from thread (lambda)!n"; } }, 5);// Create thread, pass Lambda expression and arguments t3.join();// Wait for thread to complete return 0; } After compiling with g++ -std=c++11, the execution output is: Hello from thread (lambda)!Hello from thread (lambda)!Hello from thread (lambda)!Hello from thread (lambda)!Hello from thread (lambda)! * * * ## Thread Management ### join() `join()` is used to wait for a thread to complete its execution. If you destroy the thread object without calling `join()` or `detach()`, it will cause the program to crash. t.join(); ### detach() `detach()` separates the thread from the main thread, allowing it to run independently in the background. The main thread no longer waits for it. t.detach(); * * * ## Passing Arguments to Threads ### Pass by Value Arguments can be passed to a thread by value: std::thread t(func, arg1, arg2); ### Pass by Reference If you need to pass reference arguments, you must use `std::ref`: ## Example #include #include void increment(int& x){ ++x; } int main(){ int num =0; std::thread t(increment, std::ref(num));// Use std::ref to pass a reference t.join(); std::cout<<"Value after increment: "<< num << std::endl; return 0; } * * * ## Comprehensive Example The following is a complete example demonstrating how to create threads using the three methods mentioned above and manage them. ## Example #include #include using namespace std; // A simple function to serve as the thread entry point void foo(int Z){ for(int i =0; i < Z; i++){ cout<<"Thread using function pointer as callable argumentn"; } } // Class definition for a callable object class ThreadObj { public: void operator()(int x)const{ for(int i =0; i < x; i++){ cout<<"Thread using function object as callable argumentn"; } } }; int main(){ cout<<"Threads 1, 2, 3 run independently"<< endl; // Create thread using function pointer thread th1(foo, 3); // Create thread using function object thread th2(ThreadObj(), 3); // Create thread using Lambda expression thread th3([](int x){ for(int i =0; i < x; i++){ cout<<"Thread using lambda expression as callable argumentn"; } }, 3); // Wait for all threads to complete th1.join();// Wait for thread th1 to complete th2.join();// Wait for thread th2 to complete th3.join();// Wait for thread th3 to complete return 0; } Compile with C++ 11 flag **-std=c++11**: g++ -std=c++11 test.cpp The output of the above code may vary on different platforms or each time it runs, because the execution order of threads is determined by the operating system's scheduling algorithm. Multiple threads run concurrently, and the output may be interleaved, for example: Threads 1, 2, 3 run independentlyThread using function pointer as callable argumentThread using function object as callable argumentThread using lambda expression as callable argumentThread using function pointer as callable argument... * * * ## Thread Synchronization and Mutual Exclusion In multithreading programming, thread synchronization and mutual exclusion are two very important concepts. They are used to control multiple threads' access to shared resources to avoid issues like data races and deadlocks. ### 1. Mutex A mutex is a synchronization primitive used to prevent multiple threads from accessing a shared resource simultaneously. When a thread needs to access a shared resource, it must first lock the mutex. If the mutex is already locked by another thread, the requesting thread will be blocked until the mutex is unlocked. `std::mutex`: Used to protect shared resources and prevent data races. std::mutex mtx; mtx.lock(); // Lock the mutex// Access shared resource mtx.unlock(); // Unlock the mutex `std::lock_guard` and `std::unique_lock`: Automatically manage lock acquisition and release. std::lock_guard lock(mtx); // Automatically lock and unlock// Access shared resource Example of using a mutex: ## Example #include std::mutex mtx;// Global mutex void safeFunction(){ mtx.lock();// Request to lock the mutex // Access or modify shared resource mtx.unlock();// Release the mutex } int main(){ std::thread t1(safeFunction); std::thread t2(safeFunction); t1.join(); t2.join(); return 0; } ### 2. Locks C++ provides several lock types to simplify the use and management of mutexes. Common lock types include: * `std::lock_guard`: A scoped lock that automatically locks the mutex upon construction and unlocks it upon destruction. * `std::unique_lock`: Similar to `std::lock_guard`, but provides more flexibility, such as the ability to transfer ownership and manually unlock. Example of using locks: ## Example #include std::mutex mtx; void safeFunctionWithLockGuard(){ std::lock_guard lk(mtx); // Access or modify shared resource } void safeFunctionWithUniqueLock(){ std::unique_lock ul(mtx); // Access or modify shared resource // ul.unlock(); // Optional: manually unlock // ... } ### 3. Condition Variable Condition variables are used for coordination between threads, allowing one or more threads to wait for a certain condition to occur. They are typically used in conjunction with mutexes to achieve synchronization between threads. `std::condition_variable` is used to implement waiting and notification mechanisms between threads. std::condition_variable cv; std::mutex mtx;bool ready = false; std::unique_lock lock(mtx); cv.wait(lock, []{ return ready; }); // Wait for the condition to be met// Execute after the condition is met Example of using a condition variable: ## Example #include #include std::mutex mtx; std::condition_variable cv; bool ready =false; void workerThread(){ std::unique_lock lk(mtx); cv.wait(lk, []{return ready;});// Wait for the condition // Perform work when the condition is met } void mainThread(){ { std::lock_guard lk(mtx); // Prepare data ready =true; }// Unlock when leaving scope cv.notify_one();// Notify one waiting thread } ### 4. Atomic Operations Atomic operations ensure that access to shared data is indivisible. In a multithreading environment, an atomic operation either completes entirely or does not execute at all, without an intermediate state. Example of using atomic operations: ## Example #include #include std::atomic count(0); void increment(){ count.fetch_add(1, std::memory_order_relaxed); } int main(){ std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); return count;// Should return 2 } ### 5. Thread Local Storage (TLS) Thread Local Storage allows each thread to have its own copy of data. This can be achieved using the `thread_local` keyword, avoiding contention for shared resources. Example of using Thread Local Storage: ## Example #include #include thread_local int threadData =0; void threadFunction(){ threadData =42;// Each thread has its own copy of threadData std::cout<<"Thread data: "<< threadData << std::endl; } int main(){ std::thread t1(threadFunction); std::thread t2(threadFunction); t1.join(); t2.join(); return 0; } ### 6. Deadlock and Avoidance Strategies A deadlock occurs when multiple threads are waiting for each other to release resources, but none can proceed. Strategies to avoid deadlocks include: * Always request resources in the same order. * Use timeouts when attempting to acquire resources. * Use deadlock detection algorithms. * * * ## Inter-thread Communication `std::future` and `std::promise`: Implement value passing between threads. ## Example std::promise p; std::future f = p.get_future(); std::thread t([&p]{ p.set_value(10);// Set value, triggering the future }); int result = f.get();// Get the value Message queues (based on `std::queue` and `std::mutex`) for simple inter-thread communication. * * * C++17 introduced the parallel algorithms library (``), where some algorithms support parallel execution, allowing you to leverage multi-core CPUs for performance improvement. ## Example #include #include #include std::vector vec ={1, 2, 3, 4, 5}; std::for_each(std::execution::par, vec.begin(), vec.end(), [](int&n){ n *=2; }); ## Parallel Algorithms in the C++ Standard Library > For more examples, see: > > > C++ Multithreading: [ > > > **C++ std::thread**: [
← Lua DebugLua Functions β†’