先来看c++解决多线程中保护共享数据问题的第一个概念——互斥量。
互斥量的概念
互斥量需小心使用,保护的数据少了起不到保护的效果,还可能出现异常。保护的数据多了,会影响程序的运行效率,因为锁没有释放前,别的线程会阻塞等待锁的释放。
lock和unlock的使用规则:必须配对使用。
#include<thread>
#include<iostream>
#include<list>
#include<mutex>
using namespace std;
class A {
public:
//把收到的消息放入队列的线程
void inMsgRecvQueue() {
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
{
//std::lock_guard<std::mutex> sbguard(my_mutex);
my_mutex.lock(); //要操作共享数据,所以,先加锁
msgRecvQueue.push_back(i); //假设这个数字就是我收到的命令,我直接放到消息队列里来
my_mutex.unlock(); //共享数据操作完毕,解锁
}//执行到这里sbguard的析构函数就会调用mutex的unlock
}
}
bool outMsgLULProc(int& command)
{
my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();//返回第一个元素但不检查元素存在与否
msgRecvQueue.pop_front();
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
//把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; 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_mutex; //创建互斥量
std::mutex my_mutex2; //创建互斥量
};
int main()
{
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja); //注意这里第二个参数必须是引用(用std::ref也可以),才能保证线程里用的是同一个对象
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutnMsgObj.join();
cout << "main主函数执行结束!" << endl;
}
return 0;
}
std::lock_guard类模板
std::lock_guard类模板可以直接用来取代lock和unlock
改造outMsgLULProc的代码
bool outMsgLULProc(int& command)
{
std::lock_guard<std::mutex> sbguard(my_mutex); //sbguard是随便起的变量名
//my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();//返回第一个元素但不检查元素存在与否
msgRecvQueue.pop_front();
my_mutex.unlock();
return true;
}
//my_mutex.unlock();
return false;
}
可以修改inMsgRecvQueue()的代码如下
void inMsgRecvQueue() {
for (int i = 0; i < 100; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
{
std::lock_guard<std::mutex> sbguard(my_mutex);
//my_mutex.lock(); //要操作共享数据,所以,先加锁
msgRecvQueue.push_back(i); //假设这个数字就是我收到的命令,我直接放到消息队列里来
//my_mutex.unlock(); //共享数据操作完毕,解锁
}//执行到这里sbguard的析构函数就会调用mutex的unlock
}
}
死锁
std::lock函数模板
std::lock函数模板能一次锁住两个或者两个以上(不可以是一个)的互斥量,其不存在多个线程中因为锁的顺序问题导致死锁的风险。如果锁某个互斥量失败了,会把已经锁定的互斥量解锁,等待所有的互斥量都能锁定的时候。要么所有的互斥量都锁住了,要么都没锁住。进程会卡在std::lock那里,等所有的互斥量都锁住,std::lock才返回。
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex, my_mutex2); //相当于每个互斥量都调用了lock
msgRecvQueue.push_back(i);
my_mutex2.unlock(); //前面锁住2个,后面就得解锁2个
my_mutex.unlock();
}
}
bool outMsgLULProc(int& command)
{
std::lock(my_mutex2, my_mutex); //两个顺序谁在前谁在后无所谓
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex.unlock(); //先unlock谁后unlock谁并没关系
my_mutex2.unlock();
return true;
}
my_mutex2.unlock();
my_mutex.unlock();
return false;
}
std::lock_guard的std::adopt_lock参数
void inMsgRecvQueue() {
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex, my_mutex2);
std::lock_guard<std::mutex> sbguard1(my_mutex, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);
}
}