1, C + + multithreading
1.1 thread creation
Create other threads by creating objects of the STD:: thread class. Each STD:: thread object can be associated with a thread.
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <thread> #include <iostream> int k = 0; void fun(void) { //Thread sleep, chrono is the time-dependent Library of c++11. std::this_thread::sleep_for(std::chrono::seconds(3)); for(int i = 0; i < 10; ++i) { std::cout << "hello world" << std::endl; k++; } } int main(int argc, char *argv[]) { //Create thread object std::thread t1(fun); //Output thread id and cpu core count std::cout << "ID:" << t1.get_id() << std::endl; std::cout << "CPU:" << std::thread::hardware_concurrency() << std::endl; //The main function blocks waiting for the thread to end t1.join(); //The main function and thread function are executed separately, and the thread becomes a background thread //t1.detach(); std::cout << k << std::endl; return EXIT_SUCCESS; }
The function fun will run in the thread object t1, and the join function will block the thread until the execution of the thread function ends. If the thread function has a return value, the return value will be ignored.
detach, thread and thread object are separated, and the thread is executed as a background thread. The current thread will not be blocked.
Attach a callback to the thread object, which will be executed when the new thread starts. These callbacks can be function pointers, function objects, Lambda functions, std::bind. The new thread will start immediately after the new thread object is created, and the callback passed will be executed in parallel with the thread that started it.
//Ordinary function void threadFunc(){ for (int i = 0; i < 1000; i++) { printf("FuncPointer %d\n", i); } return; } //Lambda function auto LambdaFunc = [](){ for (int i = 0; i < 1000; i++){ printf("Lambda %d\n", i); } }; //Functor like callback class OBJFunc { public: void operator() (){ for (int i = 0; i < 1000; i++){ printf("Object %d\n", i); } } }objFunc; int main(){ /*Create a thread using a function pointer* thread FuncThread(threadFunc); /*lambda Function create thread* thread LambdaThread(LambdaFunc); /*Creating threads using class objects* thread ObjThread(objFunc); /*Using class objects to create threading mode 2* thread ObjThread2(OBJFunc()); LambdaThread.join(); FuncThread.join(); ObjThread.join(); ObjThread2.join(); return 0; }
1.2 thread parameters
- Whether you use a reference type or a value type as a parameter. The passed in parameters are eventually copied to the thread's stack space. If you must pass parameters by reference, you need to use the std::ref() function.
- Avoid implicit conversions. Implicit conversion may occur after the main function is executed. At this time, the released memory space will be used (in the case of detach). It is recommended to construct a temporary object and pass it in when creating a thread. [
- Try to use const & to decorate the value parameters passed in. You can reduce one copy operation.
//Pass by value void FunbyCopy(int num) { ++num; } //Reference passing void FunbyRef(int & num) { ++num; } int main(){ //Pass by value std::thread task1(FunbyCopy, num); std::cout << "num is " << num << std::endl; //It is not wrapped with ref and is considered to be passed by value std::thread task2(FunbyRef, num); std::cout << "num is " << num << std::endl; //Reference passing std::thread task3(FunbyRef, std::ref(num)); std::cout << "num is " << num << std::endl; task1.join(); task2.join(); task3.join(); }
Output results:
num is 10 num is 10 num is 11
- Pass parameters by value: the thread will copy the parameters and access them
When using smart pointers, the} thread must copy the parameters and access the join thread. If the pointer refers to the external free space in the case of detach thread, an exception will be caused.
Effect of 1.2.1 simultaneous interpreting
#include <iostream> #include <string> #include <thread> using namespace std; /*Pass parameters by value* void func1(string m) { cout << "&m:" << &m; cout << endl; } /*Pass parameters by constant reference* void func2(const string& m) { cout << "&m:" << &m; cout << endl; } /*Pass parameters by extraordinary reference* void func3(string& m) { cout << "&m:" << &m; cout << endl; } /*Pass parameters as pointers*/ void func4(string* m) { cout << "m:" << m; cout << endl; *m = "yyy"; } int main() { string s = "xxxx"; cout << "&s:" << &s << endl; /*Pass parameters by value* thread t1(func1, s); t1.join(); /*Pass parameters by constant reference*/ thread t2(func2, s); t2.join(); /*Pass parameters by extraordinary reference* thread t3(func3, ref(s)); t3.join(); /*Pass parameters as pointers*/ string* s = new string("xxx"); cout << "s:" << s << endl; thread t4(func4, s); t.join(); return 0; }
(1) pass parameters by value: the thread will copy the parameters and access them. The addresses of m and s are different;
(2) Pass parameters by common reference: the thread will copy the parameters and access them. The addresses of m and s are different;
(3) The parameter is converted into reference form according to the parameter passed by extraordinary reference: std::ref(). The variable accessed by the thread and the parameter variable are at the same address
(4) Pass parameters by pointer: the pointer in the thread points to the same address as the parameter pointer.
1.2.2 detailed explanation of STD:: ref
std::ref is introduced into c++11 to get the reference of a variable. std::ref is used to consider the functional programming in c++11, such as std::bind.
std::bind uses a copy of the parameter instead of a reference, so it must be displayed that reference binding is performed using std::ref.
std::ref only attempts to simulate reference transfer, but cannot really become a reference. In the case of non template, std::ref can not realize reference transfer at all. Only when the template automatically deduces the type, ref can use the wrapper type reference_wrapper instead of the value type that would have been recognized, and reference_wrapper can be implicitly converted to the reference type of the referenced value, but it cannot be used as a & type.
When the thread method passes references, we want to use parameter references instead of shallow copies, so we must use ref for reference passing.
Application examples:
#include <functional> #include <iostream> void f(int& n1, int& n2, const int& n3) { std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n'; ++n1; // increments the copy of n1 stored in the function object ++n2; // increments the main()'s n2 // ++n3; // compile error } int main() { int n1 = 1, n2 = 2, n3 = 3; //Use std::ref to pass references std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3)); n1 = 10; n2 = 11; n3 = 12; std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n'; bound_f(); std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n'; }
Output results:
Before function: 10 11 12 In function: 1 11 12 After function: 10 12 12
2, Concurrent
Concurrency has two major requirements: one is mutual exclusion, and the other is waiting. Mutual exclusion is because there is shared data between threads, and waiting is because there is dependency between threads.
2.1 mutual exclusion
Mutex is a synchronization primitive and a means of thread synchronization, which is used to protect multiple threads from accessing shared data at the same time.
C + + provides four semantic mutexes:
- std::mutex exclusive mutex, cannot be used recursively
- std::timed_mutex is an exclusive mutex with timeout. It cannot be used recursively
- std::recursive_mutex recursive mutex without timeout function
- std::recursive_timed_mutex recursive mutex with timeout
2.1.1 exclusive mutex std::mutex
The basic interface of the mutex is very similar. The general usage is to block the thread through the lock () method until the ownership of the mutex is obtained. After the thread obtains the mutex and completes the task, it must use unlock() to release the occupation of the mutex. Lock () and unlock() must appear in pairs. try_lock() attempts to lock the mutex. It returns true if it succeeds and false if it fails. It is non blocking.
#include "mutex.hpp" #include <iostream> #include <mutex> #include <thread> namespace mutex_ { std::mutex mtx; // mutex for critical section static void print_block(int n, char c) { mtx.lock(); for (int i = 0; i<n; ++i) { std::cout << c; } std::cout << '\n'; mtx.unlock(); } int test_mutex_1() { std::thread th1(print_block, 50, '*'); std::thread th2(print_block, 50, '$'); th1.join(); th2.join(); return 0; }
Using std::lock_guard can simplify the writing of lock/unlock and is more secure because lock_guard will automatically lock the mutex when constructing, and will automatically unlock when destructing after exiting the scope, so as to avoid forgetting the unlock operation.
static void print_block(int n, char c) { std::lock_guard<std::mutex> locker(mtx); for (int i = 0; i<n; ++i) { std::cout << c; } std::cout << '\n'; mtx.unlock(); }
Getting the same mutex multiple times by the same thread will cause a deadlock problem. The same thread obtains a mutex and then obtains another mutex, but this mutex has been obtained by the current thread and cannot be released, resulting in deadlock.
2.1.2 std::unique_lock and std::lock_guard
When locking std::mutex, the lock operation is usually performed before modifying the shared data, and then the unlock operation is performed after writing. There will be a deadlock due to negligence when leaving the shared member operation area after lock.
To solve the above problems, STD:: unique is introduced into C++11_ Lock and std::lock_guard has two data structures. Through a thin package of lock and unlock, the function of automatic unlock is realized.
- std::lock_guard is a simple implementation of RAII template class with simple functions. std::lock_guard locks in the constructor and unlocks in the destructor.
- Class unique_lock is a universal mutex wrapper that allows delayed locking, time limited attempts to lock, recursive locking, ownership transfer, and use with conditional variables. unique_lock is better than lock_ The use of guard is more flexible and powerful. Using unique_lock needs to pay more time, performance and cost.
std::mutex mut; void insert_data() { std::lock_guard<std::mutex> lk(mut); queue.push_back(data); } void process_data() { std::unqiue_lock<std::mutex> lk(mut); queue.pop(); }
2.2 conditional variables
Conditional variable is a synchronization mechanism provided by C++11. It can block one or more threads. It will not arouse the currently blocked thread until it receives a notification or timeout from another thread. Conditional variables need to be used with mutexes.
c++11 provides two conditional variables:
- condition_variable, with STD:: unique_ Lock < STD:: mutex > wait;
- condition_variable_any, used with any mutex with lock and unlock semantics;
condition_variable_any is more flexible, but more efficient than condition_variable low.
c++11 provides #include < condition_ Variable > header file, where std::condition_variable can be used in combination with std::mutex. There are two important interfaces, notify_one() and wait().
wait() can make the thread sleep. In the consumer producer model, if consumers find nothing in the queue, they can sleep themselves.,
notify_one() is one of the condition variables in the wake-up wait (there may be many condition variables in the wait state at that time). When do you use notify_one() is better. Of course, it's time for the producer to put data into the queue. If there is data in the queue, you can quickly wake up the waiting thread to work.
#include <iostream> #include <deque> #include <thread> #include <mutex> #include <condition_variable> std::deque<int> q; std::mutex mu; std::condition_variable cond; //Generate this person void function_1() { int count = 10; while (count > 0) { std::unique_lock<std::mutex> locker(mu); q.push_front(count); locker.unlock(); cond.notify_one(); // Notify the generator to fetch data std::this_thread::sleep_for(std::chrono::seconds(1)); count--; } } //consumer void function_2() { int data = 0; while ( data != 1) { std::unique_lock<std::mutex> locker(mu); while(q.empty()) cond.wait(locker); // The thread stops waiting for the generator to notify data = q.back(); q.pop_back(); locker.unlock(); std::cout << "t2 got a value from t1: " << data << std::endl; } } int main() { std::thread t1(function_1); std::thread t2(function_2); t1.join(); t2.join(); return 0; }
3, Common mistakes and usage
3.1. Passing temporary objects as thread parameters
Pass a temporary object as a thread parameter. When detach() is used, if the main thread ends first, the variable will be recycled; Therefore, if detach () is used, it is not recommended to use references, and pointers must not be used. If you pass a simple type parameter such as int, it is recommended that you pass values instead of references to prevent complications
IV RAII in C + +
The full name of RAII is "Resource Acquisition is Initialization", which translates to "Resource Acquisition is Initialization", that is, apply for resource allocation in the constructor and release resources in the destructor. Because the language mechanism of C + + ensures that when an object is created, the constructor will be called automatically, and the destructor will be called automatically when the object exceeds the scope.
Smart pointers (std::shared_ptr and std::unique_ptr) are the most representative implementations of RAII. Using smart pointers, automatic memory management can be realized, and there is no need to worry about memory leakage caused by forgetting delete.
Another extended application of RAII is to realize secure state management. A typical application is to use STD:: unique in thread synchronization_ Lock or std::lock_guard manages the status of mutex std:: mutex.
The core idea of RAII is to bind resources or states with the life cycle of objects, and realize the security management of resources and states through the language mechanism of C + +. Understanding and using RAII can make the software design clearer and the code more robust.
reference:
[1] C++ 11 multithreading: https://blog.csdn.net/BlackCarDriver/article/details/105623063?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242
[2] C++11 passes parameters to the thread function: https://blog.csdn.net/c_base_jin/article/details/89420015
[3] C++11 concurrent and multithreading learning records (III): https://www.huaweicloud.com/articles/12608855.html
[4]C + + concurrency (C++11) - 03 passing parameters to threads: https://www.cnblogs.com/iszhangl/p/11607749.html
[5]std::ref() and reference in c++11: https://www.cnblogs.com/yi-mu-xi/p/9896461.html
[6] std::ref usage of C++11: https://murphypei.github.io/blog/2019/04/cpp-std-ref.html
[7] Mutex in c++11 multithreading: https://blog.csdn.net/li1615882553/article/details/85530621
[8]C++11_ lock_ Thread deadlock of guard and its solution: https://www.jianshu.com/p/cab2fbc4b794
[9] [c++11] multithreaded programming (VI) -- condition variable: https://www.jianshu.com/p/c1dfa1d40f53
[10]C++11 std::unique_lock and std::lock_guard differences and multithreading application examples: https://blog.csdn.net/znzxc/article/details/81515551
[11]Introduction to RAII in C + +: https://www.cnblogs.com/jiangbin/p/6986511.html