#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>
using namespace std;
class A
{
public://把受到的消息(玩家命令)入到一个队列的线程
void inmsgrectqueue()
{
for (int i = 0; i<10000; ++i)
{
cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
//my_mutex1.lock();//1
std::lock_guard<std::mutex> sbguard(my_mutex1);//2
msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
//my_mutex1.unlock();//1
}
}
//补充写法
//void inmsgrectqueue()
//{
// for (int i = 0; i < 100; ++i)
// {
// cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
// {
// std::lock_guard<std::mutex> sbguard(my_mutex1);//2
// msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
// }//这里用大括号保证析构lock_guard在大括号结束时进行,达到提前解锁的目的
// }
//}
bool outmsglulproc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex1);//2 程序结束时析构会自动执行unlock()这段代码里有两个return所以可以替代两个unlock();
//my_mutex1.lock();//1
if(!msgrectqueue.empty())
{
//消息不为空
int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
msgrectqueue.pop_front();//移除第一个元素,但不返回;
//my_mutex1.unlock();//1
return true;
}
//my_mutex1.unlock();//1
return false;
}
////把数据从消息队列中取出的线程
void outmsgrectqueue()
{
int command = 0;
for (int i = 0; i<10000; ++i)
{
bool result = outmsglulproc(command);
if(result==true)
{
cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
}
else
{
//消息队列为空
cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
}
private:
std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex1;//创建一个互斥量(一把锁头)
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
myinmsgobj.join();
myoutmsgobj.join();
//保护共享数据,操作的时候,某个线程用代码把共享数据锁住、操作数据、解锁
//其他想操作共享数据的线程必须等待解锁,操作、解锁
//互斥量(mutex)的基本概念
//互斥量就是个类对象,理解成一把锁,多个线程尝试lock()成员函数来加锁这把锁头,只有一个线程能锁成功(成功的标志是lock函数返回)
//如果没锁成功,那么流程卡再lock这里不断的尝试去锁这把锁头。
//互斥量使用要小心,保护数据不多也不少,少了,没达到保护效果,多了,影响效率。
//二.互斥量的用法
//(2.1)lock(),unlock();
//步骤:先lock(),操作共享数据,unlock()
//lock和unlock要成对使用,二者调用次数要相同,每调用一次lock必然要调用一次unlock
//有lock忘记unlock非常难排查
//为了防止大家忘记unlock(),引入一个叫std::lock_guard的类模板,你忘记unlock不要紧,我替你unlock;
//学习过智能指针(unique_ptr<>):你忘记释放内存不要紧,我帮你释放;
//(2.2)std::lock_guard类模板:直接取代lock和unlock,就是说,用了lock_guard后,不能再用lock和unlock了,不过只能在
//析构(比如函数return时)才会unlock,不够灵活
return 0;
}
![]()
死锁演示:
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>
using namespace std;
vector<int> g_v = { 1,2,3 };//共享数据,只读
//线程入口函数
//void myprint(int inum)
//{
// //cout << "myprint线程开始执行了,线程编号=" << inum << endl;
// //cout<< "myprint线程结束执行了,线程编号=" << inum << endl;
// cout << "id 为" << std::this_thread::get_id << "的线程打印g_v的值" << g_v[0] << g_v[1] << g_v[2] << endl;
// return;
//}
class A
{
public:
//把受到的消息(玩家命令)入到一个队列的线程
void inmsgrectqueue()
{
for (int i = 0; i<100; ++i)
{
cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
{
my_mutex1.lock();//实际工程中这两个锁头不一定挨着,可能他们需要保护不同的数据共享块
Sleep(1000);//程序执行太快的话这两个lock会同时执行,就不会发生死锁,这里加Sleep确保死锁的发生;
my_mutex2.lock();
msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
my_mutex2.unlock();
my_mutex1.unlock();
}
}
}
bool outmsglulproc(int &command)
{
my_mutex2.lock();
my_mutex1.lock();
if(!msgrectqueue.empty())
{
//消息不为空
int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
msgrectqueue.pop_front();//移除第一个元素,但不返回;
my_mutex2.unlock();
my_mutex1.unlock();
return true;
}
my_mutex2.unlock();
my_mutex1.unlock();
return false;
}
////把数据从消息队列中取出的线程
void outmsgrectqueue()
{
int command = 0;
for (int i = 0; i<10000; ++i)
{
bool result = outmsglulproc(command);
if(result==true)
{
cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
}
else
{
//消息队列为空
cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
}
private:
std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex1;//创建一个互斥量(一把锁头)
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
myinmsgobj.join();
myoutmsgobj.join();//三:死锁
//张三:站在北京 等李四 ,不挪窝
//李四:站在深圳 等张三, 不挪窝
//C++中
//比如我有两把锁(死锁这个问题是由至少两个锁头也就是两个互斥量才能产生);金锁,银锁。
//两个线程A,B
//1.比如线程A执行的时候这个线程先锁金锁,把金锁lock成功了,然后它去lock银锁
//出现了上下文切换
//2.线程B执行了,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会lock成功,线程B要去lock金锁..
//此时,死锁就产生了
//3.线程A 因为拿不到银锁头,流程走不下去(后边代码有解锁金锁头的但是流程走不下去,所以金锁头解不开)
//4.线程B因为拿不到金锁头,流程走不下去(后边代码有解锁银锁头的但是流程走不下去,所以银锁头解不开)
//线程互相等待陷入死锁
//死锁解决方案
//只要保证这两个互斥量上锁的顺序一致就不会死锁
return 0;
}
std::lock()
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>
using namespace std;
class A
{
public:
//把受到的消息(玩家命令)入到一个队列的线程
void inmsgrectqueue()
{
for (int i = 0; i<100; ++i)
{
cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
{
std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock();
msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
my_mutex1.unlock();
my_mutex2.unlock();
}
}
}
bool outmsglulproc(int &command)
{
std::lock(my_mutex2, my_mutex1);//两个互斥量先后没影响
if(!msgrectqueue.empty())
{
//消息不为空
int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
msgrectqueue.pop_front();//移除第一个元素,但不返回;
my_mutex2.unlock();
my_mutex1.unlock();//1
return true;
}
my_mutex2.unlock();
my_mutex1.unlock();//1
return false;
}
////把数据从消息队列中取出的线程
void outmsgrectqueue()
{
int command = 0;
for (int i = 0; i<10000; ++i)
{
bool result = outmsglulproc(command);
if(result==true)
{
cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
}
else
{
//消息队列为空
cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
}
private:
std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex1;//创建一个互斥量(一把锁头)
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
myinmsgobj.join();
myoutmsgobj.join();
//(3.3)std::lock()函数模板,用于处理多个互斥量的时候才出场
//能力:一次锁住两个或两个以上的互斥量(至少两个,多个不限。一个不行)
//它不存在这种因为在多个线程中 因为锁的顺序问题导致死锁的风险问题
//如果互斥量中有一个没锁柱,它就在那里等着,等所有互斥量都锁住,它才往下走(返回)
//要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另一个没锁成功,则它立即把已经锁住的解锁。
return 0;
}
std::lock_guard和std::adopt_lock参数
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include<list>
#include<mutex>
#include<Windows.h>
using namespace std;
class A
{
public:
//把受到的消息(玩家命令)入到一个队列的线程
void inmsgrectqueue()
{
for (int i = 0; i<100; ++i)
{
cout << "inmsgrectqueue()执行,插入一个元素" << i << endl;
{
std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了.lock()
std::lock_guard<std::mutex> subguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> subguard2(my_mutex2, std::adopt_lock);
msgrectqueue.push_back(i);//假设这个数字i就是收到的命令,直接弄到消息队列中去。
}
}
}
bool outmsglulproc(int &command)
{
std::lock(my_mutex1, my_mutex2);//两个互斥量先后没影响
std::lock_guard<std::mutex> subguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> subguard2(my_mutex2, std::adopt_lock);
if(!msgrectqueue.empty())
{
//消息不为空
int command = msgrectqueue.front();//返回第一个元素,但不检查元素是否存在
msgrectqueue.pop_front();//移除第一个元素,但不返回;
return true;
}
return false;
}
////把数据从消息队列中取出的线程
void outmsgrectqueue()
{
int command = 0;
for (int i = 0; i<100; ++i)
{
bool result = outmsglulproc(command);
if(result==true)
{
cout << "outmsgrectqueue()执行,取出一个元素" << command << endl;
}
else
{
//消息队列为空
cout << "outmsgrectqueue()执行,但目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
}
private:
std::list<int> msgrectqueue;// 容器,专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex1;//创建一个互斥量(一把锁头)
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myinmsgobj(&A::outmsgrectqueue, &myobja);//第二个参数是(引用,才能保证线程里用的是同一个对象)
std::thread myoutmsgobj(&A::inmsgrectqueue, &myobja);
myinmsgobj.join();
myoutmsgobj.join();
//(3.4)std::lock_guard和std::adopt_lock参数
//std::adopt_lock是个结构体对象,起一个标记作用:作用就是表示这个互斥量已经lock()
//不需要再std::lock_guard<std::mutex>构造函数里面对mutex对象进行lock了
//总结:std::lock():一次锁定多个互斥量,谨慎使用(建议一个一个锁)
return 0;
}