#include <atomic> #include <iostream> #include <thread> #include <chrono> #include <pthread.h> using namespace std; int i = 0; const int maxCnt = 1000000; void mythread() { for (int j = 0; j < maxCnt; j++) { i++; // 线程同时操作变量 } } int main() { auto begin = chrono::high_resolution_clock::now(); thread t1(mythread); thread t2(mythread); t1.join(); t2.join(); auto end = chrono::high_resolution_clock::now(); cout << "i=" << i << endl; cout << "time: " << chrono::duration_cast<chrono::microseconds>(end - begin).count() * 1e-6 << "s" << endl; // 秒计时 }可以看到在我的电脑上程序的输出为
i=1022418 time: 0.010445s很明显和我们预想的结果是不一致的,我们使用两个线程同时对该变量进行加法操作,根据运行此书来计算,结果因该为 2000000,但事实上却不是这样的,这就是因为有多个线程在对同一个变量进行写操作的时候会出现难以排查的问题,意想不到的结果。此时mutex就派上用场了,我们对程序进行稍微的改动。
std::mutex var_mutex; int i = 0; const int maxCnt = 1000000; void mythread() { for (int j = 0; j < maxCnt; j++) { var_mutex.lock(); i++; // 线程同时操作变量 var_mutex.unlock(); } }此时再运行程序可以发现结果如下,这是符合我们的预期的。
i=2000000 time: 0.09337s1.2 互斥量用法解释
.再 unlock() 解锁
std::mutex var_mutex; // 堆代码 duidaima.com int i = 0; const int maxCnt = 1000000; void mythread() { for (int j = 0; j < maxCnt; j++) { lock_guard<mutex> guard(var_mutex); i++; // 线程同时操作变量 } }输出结果为
i=2000000 time: 0.102605sstd::lock_guard 虽然用起来方便,但是不够灵活,它只能在析构函数中 unlock(),也就是对象被释放的时候,这通常是在函数返回的时候,或者通过添加代码块 { /* 代码块 */ } 限定作用域来指定释放时机。其还有一个特性是在构造的时候可以传入第二个参数为std::adopt_lock,此时在析构的时候就不会unlock了,但是此时就必须我们手动unlock了,这种使用场景也不多。
#include <pthread.h> #include <atomic> #include <chrono> #include <iostream> #include <mutex> #include <thread> #include <list> using namespace std; list<int> msgRecvQueue; // 容器(实际上是双向链表):存放玩家发生命令的队列 mutex m_mutex1; // 创建互斥量1 mutex m_mutex2; // 创建互斥量2 void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { cout << "inMsgRecvQueue exec, push an elem " << i << endl; m_mutex1.lock(); // 实际代码中,两把锁不一定同时上,它们可能保护不同的数据 m_mutex2.lock(); msgRecvQueue.push_back(i); // 假设数字 i 就是收到的玩家命令 m_mutex2.unlock(); m_mutex1.unlock(); } } bool outMsgLULProc(int &command) { m_mutex2.lock(); m_mutex1.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); // 返回第一个元素 msgRecvQueue.pop_front(); // 移除第一个元素 m_mutex1.unlock(); m_mutex2.unlock(); return true; } m_mutex1.unlock(); m_mutex2.unlock(); return false; } void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; ++i) { bool result = outMsgLULProc(command); if (result) cout << "outMsgLULProc exec, and pop_front: " << command << endl; else cout << "outMsgRecvQueue exec, but queue is empty!" << i << endl; cout << "outMsgRecvQueue exec end!" << i << endl; } } int main() { thread myInMsgObj(inMsgRecvQueue); thread myOutMsgObj(outMsgRecvQueue); myInMsgObj.join(); myOutMsgObj.join(); cout << "Hello World!" << endl; return 0; }笔者运行的时候发现程序会卡死,无法输出最后的一句话
outMsgLULProc exec, and pop_front: 271 outMsgRecvQueue exec end!289 outMsgLULProc exec, and pop_front: 272 outMsgRecvQueue exec end!290 inMsgRecvQueue exec, push an elem 491通常来讲,死锁的一般解决方案,只要保证多个互斥量上锁的顺序一致,就不会出现死锁,比如把上面示例代码的两个线程回调函数中的上锁顺序改一下,保持一致就好了(都改为先上锁1,再上锁2)。读者可以自己试一下改动下代码。
void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { cout << "inMsgRecvQueue exec, push an elem " << i << endl; // m_mutex1.lock(); // 实际代码中,两把锁不一定同时上,它们可能保护不同的数据 // m_mutex2.lock(); std::lock(m_mutex1,m_mutex2); msgRecvQueue.push_back(i); // 假设数字 i 就是收到的玩家命令 m_mutex2.unlock(); m_mutex1.unlock(); } }在使用 std::lock() 函数模板锁上多个互斥量时,也必须得记得把每个互斥量解锁,此时借助 std::lock_guard 的 std::adopt_lock 参数可以省略解锁的代码。我们再稍微更改一下代码,让他看上去更modern一些。
void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { cout << "inMsgRecvQueue exec, push an elem " << i << endl; // m_mutex1.lock(); // 实际代码中,两把锁不一定同时上,它们可能保护不同的数据 // m_mutex2.lock(); std::lock(m_mutex1, m_mutex2); // 锁上两个互斥量 std::lock_guard<std::mutex> m_guard1(m_mutex1, std::adopt_lock); // 构造时不上锁,但析构时解锁 std::lock_guard<std::mutex> m_guard2(m_mutex2, std::adopt_lock); // 构造时不上锁,但析构时解锁 msgRecvQueue.push_back(i); // 假设数字 i 就是收到的玩家命令 } }