1--死锁问题
死锁问题:两个线程访问资源时加锁,但都需要对方的资源才能执行释放锁;
代码实例:下面的代码中,当线程 1 使用 my_mutex1 上锁后,会继续使用 my_mutex2 进行上锁,若此时线程 2 已经使用 my_mutex2 上锁了,也准备继续使用 my_mytex1 上锁;这时就会出现死锁问题:线程 1 掌握 my_mutex1 的资源,需要使用 my_mutex2 的资源,而线程 2 掌握 my_mutex2 的资源,需要使用 my_mutex1 的资源;两个线程都掌握各自需要的资源,导致两个线程无法继续执行,产生了死锁;
一个简单的解决方案:将两个线程的上锁顺序设计为相同;
#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥
class myClass{
public:
// 收集数据到消息队列
void inMsgRecvQueue(){
for(int i = 0; i < 100; i++){
// 模拟收集消息
std::cout << "Running inMsgRecvQueue(), insert one value: "
<< i << std::endl;
my_mutex1.lock();
my_mutex2.lock();
msgRecvqueue.push_back(i); // 消息队列存储消息
my_mutex2.unlock();
my_mutex1.unlock();
}
}
// 从消息队列取数据
void outMsgRecvQueue(){
for(int i = 0; i < 100; i++){
if(!msgRecvqueue.empty()){
my_mutex2.lock(); // 加锁
my_mutex1.lock(); // 加锁
// 取出数据
int command = msgRecvqueue.front();
msgRecvqueue.pop_front();
my_mutex1.unlock(); // 解锁
my_mutex2.unlock(); // 解锁
}
else{
std::cout << "Running outMsgRecvQueue(), "
"the msgRecvqueue is empty" << std::endl;
}
}
}
private:
std::list<int> msgRecvqueue; // 消息队列
std::mutex my_mutex1; // 创建互斥量
std::mutex my_mutex2; // 创建互斥量
};
int main(int argc, char *argv[]){
myClass sample1;
// 使用成员函数创建线程
std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
2--std::lock( )函数模板
std::lock() 可以锁住两个或两个以上的互斥量,可以避免因为互斥量上锁顺序不同而导致的死锁问题;
std::lock() 一般用于处理多个互斥量,其要么将多个互斥量同时锁住,要么将多个互斥量解锁(当部分互斥量未上锁时,要么等待其余互斥量上锁才继续执行,要么释放已经锁住的互斥量);
下面的实例代码使用 std::lock() 来解决上面的死锁问题;
#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥
class myClass{
public:
// 收集数据到消息队列
void inMsgRecvQueue(){
for(int i = 0; i < 100; i++){
// 模拟收集消息
std::cout << "Running inMsgRecvQueue(), insert one value: "
<< i << std::endl;
std::lock(my_mutex1, my_mutex2);
msgRecvqueue.push_back(i); // 消息队列存储消息
my_mutex2.unlock();
my_mutex1.unlock();
}
}
// 从消息队列取数据
void outMsgRecvQueue(){
for(int i = 0; i < 100; i++){
if(!msgRecvqueue.empty()){
std::lock(my_mutex1, my_mutex2);
// 取出数据
int command = msgRecvqueue.front();
msgRecvqueue.pop_front();
my_mutex1.unlock(); // 解锁
my_mutex2.unlock(); // 解锁
}
else{
std::cout << "Running outMsgRecvQueue(), "
"the msgRecvqueue is empty" << std::endl;
}
}
}
private:
std::list<int> msgRecvqueue; // 消息队列
std::mutex my_mutex1; // 创建互斥量
std::mutex my_mutex2; // 创建互斥量
};
int main(int argc, char *argv[]){
myClass sample1;
// 使用成员函数创建线程
std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
3--std::lock_guard( )函数模板
std::lock_guard( ) 可以替换 mutex.lock() 和 mutex.unlock(),在项目复杂的时候可以避免遗漏上锁和解锁的问题;
std::lock_guard<std::mutex> 对象名(互斥量, std::adopt_lock); std::adopt_lock 的作用是表明互斥量在之前已经上锁了,无需额外对互斥量进行上锁操作,只需进行解锁操作即可;
#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥
class myClass{
public:
// 收集数据到消息队列
void inMsgRecvQueue(){
for(int i = 0; i < 100; i++){
// 模拟收集消息
std::cout << "Running inMsgRecvQueue(), insert one value: "
<< i << std::endl;
std::lock(my_mutex1, my_mutex2);
msgRecvqueue.push_back(i); // 消息队列存储消息
std::lock_guard<std::mutex> guard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(my_mutex2, std::adopt_lock);
}
}
// 从消息队列取数据
void outMsgRecvQueue(){
for(int i = 0; i < 100; i++){
if(!msgRecvqueue.empty()){
std::lock(my_mutex1, my_mutex2);
// 取出数据
int command = msgRecvqueue.front();
msgRecvqueue.pop_front();
std::lock_guard<std::mutex> guard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(my_mutex2, std::adopt_lock);
}
else{
std::cout << "Running outMsgRecvQueue(), "
"the msgRecvqueue is empty" << std::endl;
}
}
}
private:
std::list<int> msgRecvqueue; // 消息队列
std::mutex my_mutex1; // 创建互斥量
std::mutex my_mutex2; // 创建互斥量
};
int main(int argc, char *argv[]){
myClass sample1;
// 使用成员函数创建线程
std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}