互斥量可以理解成一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程可以锁定成功(成功的标志有lock()返回),如果没有锁成功,那么就会在lock()这里不断的尝试。
#include
步骤:先lock()给共享数据加锁,操作共享数据,然后再unlock()解锁。
规则:lock()和unlock()要成对使用,每调用一次lock(),不然调用一次unlock()。
解决共享数据访问冲突问题:
#include
#include
#include
#include
class A
{
public:
//把收到的消息(玩家命令)写入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;
m_mutex.lock();
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
m_mutex.unlock();
}
}
bool outMsgLockPro(int &command)
{
m_mutex.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();//移除list首元素
m_mutex.unlock();
return true;
}
m_mutex.unlock();
return false;
}
//把收据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLockPro(command);
if (result == true)
{
std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
//可以考虑进行命令(数据)处理
}
else
{
std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
}
}
std::cout << "END" << std::endl;
}
private:
std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
std::mutex m_mutex;//创建互斥量
};
int main()
{
A myobj;
std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
m_outMsgThread.join();
m_inMsgThread.join();
getchar();
return 0;
}
为了防止忘记unlock(),引入std::lock_guard
的类模板,当忘记unlock()时,std::lock_guard来执行unlock()。
当使用std::lock_guard
类模板时,就不用再使用lock()和unlock()对共享数据进行加锁。
原理是:lock_guard类构函数中执行了mutex::lock(),而其析构函数中执行了mutex::unlock()。
缺陷:没有lock()和unlock()灵活。
#include
#include
#include
#include
#include
class A
{
public:
//把收到的消息(玩家命令)写入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;
{
std::lock_guardlockGuard(m_mutex);
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
}
//m_mutex.lock();
//msgRecvQueue.push_back(i);//假设数字i就是收到的命令
//m_mutex.unlock();
}
}
bool outMsgLockPro(int &command)
{
std::lock_guard lockGuard(m_mutex);
//m_mutex.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();//移除list首元素
//m_mutex.unlock();
return true;
}
//m_mutex.unlock();
return false;
}
//把收据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLockPro(command);
if (result == true)
{
std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
//可以考虑进行命令(数据)处理
}
else
{
std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
}
}
std::cout << "END" << std::endl;
}
private:
std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
std::mutex m_mutex;//创建互斥量
};
int main()
{
A myobj;
std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
m_outMsgThread.join();
m_inMsgThread.join();
getchar();
return 0;
}
死锁问题是由至少两个锁头也就是两个互斥量(mutex)才能产生。
死锁案例:如有两互斥量lock1,lock2,且有两个线程A,B。
① 在线程A执行的时候,此线程先锁lock1并且成功了,这个时候准备去锁lock2…
② 此时,出现了上下文切换,线程B开始执行。这个线程先锁lock2,因为lock2还没有被锁,所以lock2会lock()成功。于是,线程B要去锁lock1…
③ 此时,线程A因为拿不到锁lock2,流程走不下去(虽然后面代码有unlock锁lock1的,但是流程走不下去,所以lock1解不开);同理,线程B因为拿不到锁lock1,流程走不下去(虽然后面代码有unlock锁lock2的,但是流程走不下去,所以lock2解不开)。这样,死锁就产生了。
死锁的实例如下:
#include
#include
#include
#include
#include
class A
{
public:
//把收到的消息(玩家命令)写入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
m_mutex2.unlock();
m_mutex1.unlock();
}
}
bool outMsgLockPro(int &command)
{
m_mutex2.lock();
m_mutex1.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();//移除list首元素
m_mutex1.unlock();
m_mutex2.unlock();
return true;
}
m_mutex1.unlock();
m_mutex2.unlock();
return false;
}
//把收据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLockPro(command);
if (result == true)
{
std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
//可以考虑进行命令(数据)处理
}
else
{
std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
}
}
std::cout << "END" << std::endl;
}
private:
std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
std::mutex m_mutex1;//创建互斥量
std::mutex m_mutex2;
};
int main()
{
A myobj;
std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
m_outMsgThread.join();
m_inMsgThread.join();
getchar();
return 0;
}
只要保证两个互斥量的上锁顺序一致就不会造成死锁。
对上面死锁的修正:
#include
#include
#include
#include
#include
class A
{
public:
//把收到的消息(玩家命令)写入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
m_mutex2.unlock();
m_mutex1.unlock();
}
}
bool outMsgLockPro(int &command)
{
m_mutex1.lock();
m_mutex2.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();//移除list首元素
m_mutex2.unlock();
m_mutex1.unlock();
return true;
}
m_mutex2.unlock();
m_mutex1.unlock();
return false;
}
//把收据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLockPro(command);
if (result == true)
{
std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
//可以考虑进行命令(数据)处理
}
else
{
std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
}
}
std::cout << "END" << std::endl;
}
private:
std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
std::mutex m_mutex1;//创建互斥量
std::mutex m_mutex2;
};
int main()
{
A myobj;
std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
m_outMsgThread.join();
m_inMsgThread.join();
getchar();
return 0;
}
作用:一次锁住两个或者两个以上
的互斥量,且不存在再多个线程中,因为锁的顺序问题导致死锁的风险问题。
std::lock()
:如果互斥量中有一个没锁住,它就等待所有互斥量都锁住,才继续往下走。特点是:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,而另外一个没锁成功,则立即把已经锁住的解锁。
std::lock()的应用方式:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::lock(m_mutex1, m_mutex2);
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
m_mutex2.unlock();
m_mutex1.unlock();
}
}
std::adopt_lock
参数作用:表示此互斥量已经lock(),不需要在std::lock_guard
构造函数里面再次对对象在此lock()。
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::lock(m_mutex1, m_mutex2);//防止死锁且自动lock
std::lock_guardlockGuard1(m_mutex1, std::adopt_lock);//自动解锁且不用再加锁(std::adopt_lock)
std::lock_guardlockGuard2(m_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
}
}