#include<iostream> #include<thread> #include<vector> #include<string> #include<mutex> #include<list> using namespace std; class A { public: //把收到的消息(玩家命令)入到一个队列的线程。 void inMsgRecvQueue() { for (int i = 0; i < 10000; i++) { cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl; /*my_mutex1.lock(); my_mutex2.lock();*///产生死锁,实际工程这两个锁头不一定挨着,可能他们需要保护不同的数据共享块 /*std::lock_guard<std::mutex> sbguard(my_mutex1); std::lock_guard<std::mutex> sbguard2(my_mutex2);*/
std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了lock std::lock_guard<std::mutex> sbguard(my_mutex1, std::adopt_lock);//adopt_lock()参数表示 std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);//假设数字i就是玩家收到的命令,我直接弄到消息队列里面 /*my_mutex2.unlock(); my_mutex1.unlock();*/ } return; }
bool outMsgLULProc(int &command) { //std::lock_guard<std::mutex> sbguard(my_mutex1);//sbguard是随便起的对象名 //lock_guard()构造函数里执行了mutex::lock(); //lock_guard()析构函数里执行了mutex::unlock(); //可以加{}作用域,提前结束lock_guard()的生命周期来执行析构函数,执行unlock() //my_mutex.lock();这时候就不能用lock()和unlock()
/*std::lock_guard<std::mutex> sbguard(my_mutex1); std::lock_guard<std::mutex> sbguard2(my_mutex2);*/ /*my_mutex2.lock(); my_mutex1.lock();*/ std::lock(my_mutex2, my_mutex1); std::lock_guard<std::mutex> sbguard(my_mutex1, std::adopt_lock);//adopt_lock()参数表示不通过lock_guard的构造函数调用这两个互斥量的构造函数,因为std::lock()已经lock()过了 std::lock_guard<std::mutex> sbguard2(my_mutex2, adopt_lock); if (!msgRecvQueue.empty()) { //消息队列不会空 command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front();//移除第一个元素。但不返回。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } /*my_mutex1.unlock(); my_mutex2.unlock();*/ return false; }
//把数据从消息队列中取出的线程: void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 10000; i++) { bool result = outMsgLULProc(command); if (result == true) { cout << "outMsgRecvQueue执行了,取出一个元素,并且移除了一个元素" << command << endl; //可以进行数据处理 } else { cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << endl; } } cout << "end" << endl; } private: std::list<int> msgRecvQueue;//容器,专门用于代表玩家给咱们发送的命令 std::mutex my_mutex1;//创建了一个互斥量 std::mutex my_mutex2;//创建了一个互斥量。两把锁才能产生死锁 }; int main() { /* 保护共享数据,操作时,某个线程用代码把共享数据锁住,其他想操作共享数据的线程必须等待。等待解锁。解锁后,其他线程才能操作数据。 (1)互斥量(mutex)的基本概念 互斥量就是一个类对象。理解成一把锁多个线程尝试用互斥量这个类对象的成员函数lock()来加锁。只有一个线程能够锁定成功,(成功的标志是lock()的返回值)) 如果没有锁定成功,那么这个线程中的执行流程会卡在lock()这里,并且不断的尝试去锁这把锁头,锁成功后才能继续执行。 * (2)互斥量的用法 * (2.1)lock(),unlock() * 步骤:先lock(),操作共享数据,unlock(); * lock()和unlock()要成对使用,有lock()必然要有unlock(),每调用一次lock(),必然应该调用一次unlock(); * 不应该也不允许调用一次lock()却调用两次unlock()等等,这些非对称数量的调用,都会导致代码不稳定甚至崩溃。 * 有lock,忘记unlock的问题,非常难排查; * 为了防止大家忘记unlock,引入了一个std::lock_guard()的类模板:你忘了unlock()不要紧,我替你unlock() * 和指针之类类似:你忘记释放内存,我替你释放。 * (2.2)std::lock_guard()类模板:可以直接取代lock()和unlock(),也就说你用了std::lock_guard()再不能使用lock()和unlock() * 可以用{}作用域来让lock_goard()提前执行析构函数,在作用域外面不要进行读写操作。 * std::lock_guard<std::mutex> sbguard(my_mutex);//sbguard是随便起的对象名 (3)死锁 C++中:一把锁就是一个互斥量 比如我有两把锁,(死锁这个问题产生的前提条件是至少有两个互斥量,一个不会产生);金锁(jinlock),银锁(yinlock) (1)两个线程A,B,线程A执行的时候,这个线程先锁金锁,把金锁lock()成功了,然后lock()银锁。。出现了上下文切换 (2)线程B执行的时候,这个线程先锁银锁,吧银锁lock()成功了,然后lock()金锁。此时此刻死锁就产生了 (3)线程A拿不到银锁头,流程走不下去(所以后边的代码有解锁金锁头的但是流程走不下去,所以金锁头解不开) (3)线程B拿不到金锁头,流程走不下去(所以后边的代码有解锁银锁头的但是流程走不下去,所以银锁头解不开) (3.1)死锁演示 (3.2)死锁的一般解决方案 只要保证这两个互斥量上锁的顺序一致就不会死锁。 (3.3)std::lock()函数模板:用来处理多个互斥量,不过还是得用unlock一个一个解 能力:一次能锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) 它不存在这种因为在多个线程中,因为锁的顺序问题导致死锁的风险问题; std::lock()如果互斥量中有一个没锁住,它就先把锁住的互斥量释放掉,它就在那里等着,等所有的互斥量都锁住,它才能往下走(返回) 特点:要么两个都锁住,要么两个都没锁住。如果只锁了一个,另外一个没锁成功,则他立即把已经锁住的解锁。 (3.4)std::lock_guard的std::adopt_lock参数 std::adopt_lock()是一个结构体对象,表示这个互斥量已经lock()过了,不需要在std::lock_guard<std::mutex>里面对mutex对象进行lock(),析构上正常 总结:std::lock()一次锁定多个互斥量;谨慎使用(建议一个一个锁), */ A myobj; std::thread mytobj(&A::inMsgRecvQueue, &myobj);//第二个参数是引用,才能保证线程里用的是同一个对象。 std::thread mytobj2(&A::outMsgRecvQueue, &myobj); mytobj.join(); mytobj2.join(); return 0; }