1. Create a thread
Creating a thread is relatively simple. Instantiating a thread object with std thread completes the creation. Example:
1 #include <iostream> 2 #include <thread> 3 using namespace std; 4 5 void t1() //Ordinary functions used to execute threads 6 { 7 for (int i = 0; i < 20; ++i) 8 { 9 cout << "t1111\n"; 10 } 11 } 12 void t2() 13 { 14 for (int i = 0; i < 20; ++i) 15 { 16 cout << "t22222\n"; 17 } 18 } 19 int main() 20 { 21 thread th1(t1); //Instantiate a thread object th1, construct it with function T1, and then the thread starts to execute (t1()) 22 thread th2(t2); 23 24 cout << "here is main\n\n"; 25 26 return 0; 27 }
However, there is a problem with this example, because after the thread is created, the thread starts to execute, but the main thread () does not stop. It still continues to execute and then exits. At this time, the thread object is still joinable. The thread still exists, but the thread object pointing to it has been destroyed, so an exception will be thrown.
So how to ensure that the child thread exits the main thread after it finishes executing?
2.thread::join()
The above problems can be solved by using the join interface. The function of join is to make the main thread wait until the execution of the sub thread ends. Example:
#include <iostream> #include <thread> using namespace std; void t1() { for (int i = 0; i < 20; ++i) { cout << "t1111\n"; } } void t2() { for (int i = 0; i < 20; ++i) { cout << "t22222\n"; } } int main() { thread th1(t1); thread th2(t2); th1.join(); //Wait for th1 execution to complete th2.join(); //Wait for th2 execution to complete cout << "here is main\n\n"; return 0; }
At this time, the sub thread can be executed normally. At the same time, pay attention to the last output, which shows that main waits for the end of the sub thread before continuing to execute
It should be noted that after the thread object executes the join, it is no longer joinable, so it can only be called once.
3.thread::detach()
(1.) the problem mentioned in can also be solved by detach. Detach is used to separate from the thread object, so that the thread can execute independently. However, since there is no thread object pointing to the thread, it loses its control. When the object is destructed, the thread will continue to execute in the background, but when the main program exits, the thread cannot be guaranteed to complete execution. If there is no good control mechanism or this background thread is more important, it is better to use join instead of detach.
int main() { thread th1(t1); thread th2(t2); th1.detach(); th2.detach(); cout << "here is main\n\n"; return 0; }
It can be seen from the results that the thread exited without completing the execution:
4.mutex
The header file is mutex, which is used to ensure thread synchronization and prevent different threads from operating the same shared data at the same time.
int cnt = 20; mutex m; void t1() { while (cnt > 0) { m.lock(); if (cnt > 0) { --cnt; cout << cnt << endl; } m.unlock(); } } void t2() { while (cnt > 0) { m.lock(); if (cnt > 0) { --cnt; cout << cnt << endl; } m.unlock(); } } int main() { thread th1(t1); thread th2(t2); th1.join(); th2.join(); return 0; }
As a result of running, cnt decreases in sequence, and the sequence is not disturbed by multithreading:
But using mutex is not safe. When a thread exits abnormally before unlocking, other blocked threads cannot continue.
5.std::lock_guard
Using lock_guard is relatively safe. It is scope based and can unlock itself. When the object is created, it will obtain a mutex like m.lock(). When the life cycle ends, it will automatically unlock and will not affect other threads because of the abnormal exit of a thread. Example:
int cnt = 20; mutex m; void t1() { while (cnt > 0) { lock_guard<mutex> lockGuard(m); if (cnt > 0) { --cnt; cout << cnt << endl; } } } void t2() { while (cnt > 0) { lock_guard<mutex> lockGuard(m); if (cnt > 0) { --cnt; cout << cnt << endl; } } }