參考引用
- C++11 14 17 20 多線程從原理到線程池實戰
- 代碼運行環境:Visual Studio 2019
1. 多線程狀態
1.1 線程狀態說明
- 初始化 (lnit):該線程正在被創建
- 就緒 (Ready):該線程在就緒列表中,等待 CPU 調度
- 運行 (Running):該線程正在運行
- 阻塞 (Blocked):該線程被阻塞掛起,Blocked 狀態包括
- pend (鎖、事件、信號量等阻塞)
- suspend (主動 pend)
- delay (延時阻塞)
- pendtime (因為鎖、事件、信號量時間等超時)
- 退出 (Exit):該線程運行結束,等待父線程回收其控制塊資源
- 告訴操作系統把該線程相關資源釋放,不包含堆中的資源釋放
1.2 競爭狀態和臨界區
- 競爭狀態 (Race Condition)
- 多線程同時讀寫共享數據
- 臨界區 (Critical Section)
- 讀寫共享數據的代碼片段(lock 和 unlock 之間的代碼)
避免競爭狀態策略,對臨界區進行保護,同時只能有一個線程進入臨界區
2. 互斥體和鎖
2.1 互斥鎖
- thread_mutex.cpp
#include <iostream> #include <thread> #include <string> #include <mutex>using namespace std;static mutex mux;void TestThread() {for (;;){ // 獲取鎖資源,如果沒有則阻塞等待(一次只能有一個線程拿到鎖)// 拿鎖的原則:盡晚申請、盡早釋放//mux.lock(); // 拿鎖方式一 if (!mux.try_lock()) { // 拿鎖方式二:可以看到多個進程在競爭拿鎖的情況cout << "." << flush;this_thread::sleep_for(100ms);continue;}// 業務代碼cout << "=========" << endl;cout << "Test 001" << endl;cout << "Test 002" << endl;cout << "Test 003" << endl;cout << "=========" << endl;mux.unlock(); // 如果忘記釋放鎖,則會導致死鎖,所有線程都在等待this_thread::sleep_for(1000ms);} }int main(int argc, char* argv[]) {// 同時創建 10 個線程for (int i = 0; i < 10; i++) {thread th(TestThread);th.detach();}getchar();return 0; }
2.2 線程搶占不到資源
- thread_mutex2.cpp
#include <iostream> #include <thread> #include <string> #include <mutex>using namespace std;static mutex mux;void ThreadMainMux(int i) {for (;;){ mux.lock();cout << i << "[in]" << endl;this_thread::sleep_for(1000ms);mux.unlock();// 防止 unlock() 還未釋放完全就進入下一個 lock(),導致其他線程拿不到鎖this_thread::sleep_for(1ms);} }int main(int argc, char* argv[]) {// 同時創建 3 個線程for (int i = 0; i < 3; i++) {thread th(ThreadMainMux, i + 1);th.detach();}getchar();return 0; }
2.3 超時鎖 timed_mutex 應用
- 可以記錄鎖的獲取情況,多次超時,可以記錄日志,獲取錯誤情況,避免長時間死鎖
- timed_mutex.cpp
#include <iostream> #include <thread> #include <string> #include <mutex>using namespace std;timed_mutex tmux; // 支持超時的互斥鎖void ThreadMainTime(int i) {for (;;){ if (!tmux.try_lock_for(chrono::milliseconds(500))) {cout << i << "[try_lock_for timeout]" << endl;continue;}cout << i << "[in]" << endl;this_thread::sleep_for(2000ms);tmux.unlock();// 防止 unlock() 還未釋放完全就進入下一個 lock(),導致其他線程拿不到鎖this_thread::sleep_for(1ms);} }int main(int argc, char* argv[]) {getchar();// 同時創建 3 個線程for (int i = 0; i < 3; i++) {thread th(ThreadMainTime, i + 1);th.detach();}getchar();return 0; }
2.4 遞歸鎖 recursive_mutex(可重入)
- 同一個線程中的同一把鎖可以鎖多次,避免一些不必要的死鎖
- 組合業務:用到同一個鎖
- recursive_mutex.cpp
#include <iostream> #include <thread> #include <string> #include <mutex>using namespace std;recursive_mutex rmux; // 支持可重入的互斥鎖void Task1() {rmux.lock();cout << "task1 [in]" << endl;rmux.unlock(); }void Task2() {rmux.lock();cout << "task2 [in]" << endl;rmux.unlock(); }void ThreadMainRec(int i) {for (;;){// 加鎖幾次對應的也要解鎖幾次rmux.lock();Task1();cout << i << "[in]" << endl;this_thread::sleep_for(2000ms);Task2();rmux.unlock();this_thread::sleep_for(1ms);} }int main(int argc, char* argv[]) {// 同時創建 3 個線程for (int i = 0; i < 3; i++) {thread th(ThreadMainRec, i + 1);th.detach();}getchar();return 0; }
2.5 共享鎖 shared_mutex(解決讀寫問題)
- c++14 共享超時互斥鎖 shared_timed_mutex
- 如果只有寫時需要互斥,讀取時不需要,用普通的鎖的話如何做
- 按照如下代碼,讀取只能有一個線程進入,在很多業務場景中,沒有充分利用 CPU 資源
- shared_mutex.cpp
#include <iostream> #include <thread> #include <string> #include <mutex> #include <shared_mutex>using namespace std;shared_timed_mutex stmux; // 支持可重入的共享鎖 C++14// 讀取線程 void ThreadRead(int i) {for (;;){stmux.lock_shared();cout << i << " Read" << endl;this_thread::sleep_for(500ms);stmux.unlock_shared();this_thread::sleep_for(1ms);} }// 寫入線程 void ThreadWrite(int i) {for (;;){stmux.lock_shared(); // 只要沒有鎖定互斥鎖,共享鎖都是立即返回// 讀取數據stmux.unlock_shared();// 互斥鎖 寫入(同時只能一個線程寫入),共享鎖和互斥鎖都不能進入stmux.lock(); cout << i << " Write" << endl;this_thread::sleep_for(300ms);stmux.unlock();this_thread::sleep_for(1ms);} }int main(int argc, char* argv[]) {for (int i = 0; i < 3; i++) {thread th(ThreadWrite, i + 1);th.detach();}for (int i = 0; i < 3; i++) {thread th(ThreadRead, i + 1);th.detach();}getchar();return 0; }