五、互斥量
互斥量:
是个类对象,理解成一把锁,多个线程尝试用lock()来加锁这把锁头时,只有一个线程能锁定成功。(成功的标志是lock()函数返回),如果没锁成功,那么流程卡在lock()这里不断尝试去锁这把锁头(也就是每被调用寄存器一次,都会去申请锁)。
慎用lock()保护共享数据不多也不少,少了达不到效果,多了影响效率;
lock()与unlock()要成对使用
每调用一次lock()必然要调用一次unlock(),不能调用次数不一致,否则会导致代码不稳定甚至崩溃。
#include <thread> #include <iostream> #include <list> #include <mutex> using namespace std; class A { public: void inMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { cout << "inMsgRecvQueue插入一个元素" << i << endl; mymutex.lock(); msgRecvQueue.push_back(i); mymutex.unlock(); } } bool outMsgProc(int &command) { mymutex.lock(); if (!msgRecvQueue.empty()) { int command = msgRecvQueue.front(); msgRecvQueue.pop_front(); mymutex.unlock(); //两个代码分支最后都要解锁,否则会出现未解锁情况 return true; } mymutex.unlock(); //两个代码分支最后都要解锁,否则会出现未解锁情况 return false; } void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 10000; ++i) { bool re = outMsgProc(command); if (re == true) { cout << "outMsgRecvQueue执行,取出一个元素" << command << endl; } else { cout << "消息队列为空" << endl; } } } private: std::list<int> msgRecvQueue; std::mutex mymutex; }; int main() { A myobja; std::thread myoutobj(&A::outMsgRecvQueue, &myobja); std::thread myinobj(&A::inMsgRecvQueue, &myobja); myinobj.join(); myoutobj.join(); }
std::lock_guard的类模板
你忘记了unlock,系统替你unlock();
std::lock_guard类模板:直接取代lock()和unlock():也就是说用了lock_guard之后,再不能用lock()和unlock()了;
#include <thread> #include <iostream> #include <list> #include <mutex> using namespace std; class A { public: void inMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { cout << "inMsgRecvQueue插入一个元素" << i << endl; //mymutex.lock(); { std::lock_guard<std::mutex> sbguard(mymutex); //sbguard名称随意 msgRecvQueue.push_back(i); } //mymutex.unlock(); } } bool outMsgProc(int &command) { std::lock_guard<std::mutex> sbguard(mymutex); //sbguard名称随意 //lock_guard构造函数里执行了mutex::lock() //lock_guard析构函数里执行了mutex::unlock() //mymutex.lock(); if (!msgRecvQueue.empty()) { int command = msgRecvQueue.front(); msgRecvQueue.pop_front(); //mymutex.unlock(); return true; } //mymutex.unlock(); return false; } void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 10000; ++i) { bool re = outMsgProc(command); if (re == true) { cout << "outMsgRecvQueue执行,取出一个元素" << command << endl; } else { cout << "消息队列为空" << endl; } } } private: std::list<int> msgRecvQueue; std::mutex mymutex; }; int main() { A myobja; std::thread myoutobj(&A::outMsgRecvQueue, &myobja); std::thread myinobj(&A::inMsgRecvQueue, &myobja); myinobj.join(); myoutobj.join(); }
lock_guard相对还是没有lock()/unlock()灵活。
死锁
两个线程A,B,两把锁sa,sb
1、线程A执行的时候,这个线程先锁sa锁,把sa锁 lock()成功了,然后它去lock sb锁,还未成功时,出现了上下文切换。
2、线程B开始执行,这个线程先锁 sb锁,因为 sb锁 还没有被锁,所以 sb锁 会lock()成功,线程B要去锁 sa锁
3、此时死锁就产生了,线程A因为拿不到 sb锁 流程就走不下去(sa锁 就解不开)
4、线程B因为拿不到 sa锁 流程也走不下去(sb锁 就解不开)
死锁演示:
#include <thread> #include <iostream> #include <list> #include <mutex> using namespace std; class A { public: void inMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { cout << "inMsgRecvQueue插入一个元素" << i << endl; mymutex1.lock(); //...其他操作 mymutex2.lock(); msgRecvQueue.push_back(i); mymutex2.unlock(); //...其他操作 mymutex1.unlock(); } } bool outMsgProc(int &command) { mymutex2.lock(); mymutex1.lock(); if (!msgRecvQueue.empty()) { int command = msgRecvQueue.front(); msgRecvQueue.pop_front(); mymutex1.unlock(); mymutex2.unlock(); return true; } mymutex1.unlock(); mymutex2.unlock(); return false; } void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 10000; ++i) { bool re = outMsgProc(command); if (re == true) { cout << "outMsgRecvQueue执行,取出一个元素" << command << endl; } else { cout << "消息队列为空" << endl; } } } private: std::list<int> msgRecvQueue; std::mutex mymutex1; std::mutex mymutex2; }; int main() { A myobja; std::thread myoutobj(&A::outMsgRecvQueue, &myobja); //注意这里myobja用引用,才能保证线程里用的是同一个对象 std::thread myinobj(&A::inMsgRecvQueue, &myobja); myinobj.join(); myoutobj.join(); }
运行结果:卡住不动了。
死锁的一般解决方法:只要保证两个互斥量的调用顺序保持一致。
std::lock()模板
上面的lock时是std::mutex.lock()。
能力:一次锁住两个或以上互斥量(至少两个,多了不限),如果互斥量中有一个没锁住,它就在那里等着,直到所有互斥量都锁住,它才继续执行。
它不存在,因为在多个线程中,因为锁头的lock的顺序问题导致的死锁风险问题(若没锁住全部,它会将锁住的锁释放掉);
因此:要么两个互斥量都锁住,要么都没锁住。
std::lock_guard的std::adopt_lock参数
#include <thread> #include <iostream> #include <list> #include <mutex> using namespace std; class A { public: void inMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { cout << "inMsgRecvQueue插入一个元素" << i << endl; std::lock(mymutex1, mymutex2); std::lock_guard<std::mutex> sbguard1(mymutex1, std::adopt_lock); std::lock_guard<std::mutex> sbguard2(mymutex2, std::adopt_lock); msgRecvQueue.push_back(i); //mymutex2.unlock(); //...其他操作 //mymutex1.unlock(); } } bool outMsgProc(int &command) { std::lock(mymutex1, mymutex2); std::lock_guard<std::mutex> sbguard1(mymutex1, std::adopt_lock); std::lock_guard<std::mutex> sbguard2(mymutex2, std::adopt_lock); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); msgRecvQueue.pop_front(); //mymutex1.unlock(); //mymutex2.unlock(); return true; } //mymutex1.unlock(); //mymutex2.unlock(); return false; } void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 10000; ++i) { bool re = outMsgProc(command); if (re == true) { cout << "outMsgRecvQueue执行,取出一个元素" << command << endl; } else { cout << "消息队列为空" << endl; } } } private: std::list<int> msgRecvQueue; std::mutex mymutex1; std::mutex mymutex2; }; int main() { A myobja; std::thread myoutobj(&A::outMsgRecvQueue, &myobja); //注意这里myobja用引用,才能保证线程里用的是同一个对象 std::thread myinobj(&A::inMsgRecvQueue, &myobja); myinobj.join(); myoutobj.join(); }
std::adopt_lock是一个结构体对象,起标记作用,就是表示这个互斥量已经lock(),不需要在std::lock_guard<std::mutex>里面对对象再次进行lock了
总结:
std::lock():一次性锁定多个互斥量,配合
std::lock_guard<std::mutex> sbguard1(mymutex1, std::adopt_lock);
实现不用在意加锁顺序与释放锁,但也可以手动unlock以实现细粒度锁。
std::lock() 谨慎使用(建议一个一个锁);