参考引用
- 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; }