#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;
}