您的位置:首页 > 其它

多线程互斥量以及死锁问题解决详解

2019-03-10 15:02 141 查看

多线程互斥量以及死锁问题解决详解

1.互斥量(mutex)的基本概念

  互斥量可以理解成一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程可以锁定成功(成功的标志有lock()返回),如果没有锁成功,那么就会在lock()这里不断的尝试。
  

#include <mutex>

2.互斥量的用法

(1)lock(),unlock()

  步骤:先lock()给共享数据加锁,操作共享数据,然后再unlock()解锁。
  规则:lock()和unlock()要成对使用,每调用一次lock(),不然调用一次unlock()。
  解决共享数据访问冲突问题:

#include<iostream>
#include<list>
#include<thread>
#include<mutex>

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::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列
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;
}

(2)std::lock_guard类模板

  为了防止忘记unlock(),引入

std::lock_guard
的类模板,当忘记unlock()时,std::lock_guard来执行unlock()。
  当使用
std::lock_guard
类模板时,就不用再使用lock()和unlock()对共享数据进行加锁。
  原理是:lock_guard类构函数中执行了mutex::lock(),而其析构函数中执行了mutex::unlock()。
  缺陷:没有lock()和unlock()灵活。

#include<iostream>
#include<vector>
#include<list>
#include<thread>
#include<mutex>

class A
{
public:
//把收到的消息(玩家命令)写入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;
{
std::lock_guard<std::mutex>lockGuard(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<std::mutex> 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::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列
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;
}

3.死锁

(1)死锁演示

  死锁问题是由至少两个锁头也就是两个互斥量(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<iostream>
#include<vector>
#include<list>
#include<thread>
#include<mutex>

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::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列
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;
}

(2)死锁的一般解决方案

  只要保证两个互斥量的上锁顺序一致就不会造成死锁。
  对上面死锁的修正:

#include<iostream>
#include<vector>
#include<list>
#include<thread>
#include<mutex>

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::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列
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;
}

(3)std::lock()函数模板

  作用:一次锁住

两个或者两个以上
的互斥量,且不存在再多个线程中,因为锁的顺序问题导致死锁的风险问题。
  
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();
}
}

(4)std::lock_guard()的std::adopt_lock参数

  

std::adopt_lock
参数作用:表示此互斥量已经lock(),不需要在
std::lock_guard<std::mutex>
构造函数里面再次对对象在此lock()。

void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::lock(m_mutex1, m_mutex2);//防止死锁且自动lock
std::lock_guard<std::mutex>lockGuard1(m_mutex1, std::adopt_lock);//自动解锁且不用再加锁(std::adopt_lock)
std::lock_guard<std::mutex>lockGuard2(m_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);//假设数字i就是收到的命令
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐