大纲
C++ 并发编程
互斥量
互斥量的概念
C++ 中的互斥量(mutex)是一种用于保护共享数据免受并发访问影响的同步原语。它的核心机制是 “独占锁定”:同一时刻,只有一个线程能成功锁定(lock)互斥量并进入临界区执行代码,其他试图进入的线程必须等待,直到持有锁的线程调用解锁(unlock)释放所有权。这种机制有效防止了数据竞争(Data Race)和未定义行为。C++ 11 标准库将互斥量纳入了线程支持库,并提供了 std::lock_guard、std::unique_lock 等 RAII 包装器,用于自动管理锁的获取与释放,避免因异常或忘记解锁而导致的死锁问题。
互斥量的使用
lock()、unlock()
互斥量(mutex)的基本用法遵循显式的加锁与解锁配对原则:线程必须先调用 lock() 获取互斥量,然后安全地操作共享数据,最后调用 unlock() 释放所有权。
特别注意
互斥量(mutex)的 lock() 和 unlock() 必须严格成对使用,每次 lock() 对应一次 unlock(),既不能多调用一次 unlock(),也不能少调用一次,否则会导致未定义行为或线程死锁等严重问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| #include <atomic> #include <iostream> #include <list> #include <mutex> #include <thread>
class MyClass { public: void inMsgRecvQueue() { for (int i = 0; i < 1000; ++i) { msgRecvQueueMutex.lock();
msgRecvQueue.push_back(i);
msgRecvQueueMutex.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
stop = true; }
void outMsgRecvQueue() { while (true) { int command = -1;
msgRecvQueueMutex.lock();
if (stop && msgRecvQueue.empty()) { msgRecvQueueMutex.unlock(); break; }
if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); msgRecvQueue.pop_front(); }
msgRecvQueueMutex.unlock();
if (command != -1) { std::cout << "已处理玩家命令: " << command << std::endl; }
std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
private: std::list<int> msgRecvQueue; std::mutex msgRecvQueueMutex; std::atomic_bool stop{false}; };
int main() { std::cout << "main thread start." << std::endl;
MyClass mc;
std::thread t_write(&MyClass::inMsgRecvQueue, &mc);
std::thread t_read(&MyClass::outMsgRecvQueue, &mc);
t_write.join();
t_read.join();
std::cout << "main thread end." << std::endl; return 0; }
|
程序运行的结果如下:
1 2 3 4 5 6 7 8 9
| 已处理玩家命令: 0 已处理玩家命令: 1 已处理玩家命令: 2 已处理玩家命令: 3 ...... 已处理玩家命令: 996 已处理玩家命令: 997 已处理玩家命令: 998 已处理玩家命令: 999
|
std::lock_guard 模板
C++ 11 标准库将互斥量纳入了线程支持库,并提供了 std::lock_guard、std::unique_lock 等 RAII 包装器,用于自动管理锁的获取与释放,避免因异常或忘记解锁而导致的死锁问题。值得一提的是,std::lock_guard 通过 RAII(资源获取即初始化)机制实现自动加锁和解锁:在构造时,它的构造函数会接收一个互斥量对象并立即调用该互斥量的 lock() 方法完成加锁;在析构时,它的析构函数会自动调用互斥量的 unlock() 方法完成解锁。这样,当 lock_guard 对象被创建时锁就被获取,当该对象离开作用域(无论是正常结束、return、break 还是抛出异常)时,析构函数会被自动触发,从而保证锁一定会被释放,避免了因忘记调用 unlock() 或异常导致死锁的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| #include <atomic> #include <iostream> #include <list> #include <mutex> #include <thread>
class MyClass { public: void inMsgRecvQueue() { for (int i = 0; i < 1000; ++i) { { std::lock_guard<std::mutex> lock(msgRecvQueueMutex);
msgRecvQueue.push_back(i); }
std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
stop = true; }
void outMsgRecvQueue() { while (true) { int command = -1;
{ std::lock_guard<std::mutex> lock(msgRecvQueueMutex);
if (stop && msgRecvQueue.empty()) { break; }
if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); msgRecvQueue.pop_front(); } }
if (command != -1) { std::cout << "已处理玩家命令: " << command << std::endl; }
std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
private: std::list<int> msgRecvQueue; std::mutex msgRecvQueueMutex; std::atomic_bool stop{false}; };
int main() { std::cout << "main thread start." << std::endl;
MyClass mc;
std::thread t_write(&MyClass::inMsgRecvQueue, &mc);
std::thread t_read(&MyClass::outMsgRecvQueue, &mc);
t_write.join();
t_read.join();
std::cout << "main thread end." << std::endl; return 0; }
|
程序运行的结果如下:
1 2 3 4 5 6 7 8 9
| 已处理玩家命令: 0 已处理玩家命令: 1 已处理玩家命令: 2 已处理玩家命令: 3 ...... 已处理玩家命令: 996 已处理玩家命令: 997 已处理玩家命令: 998 已处理玩家命令: 999
|
线程死锁
线程死锁的案例