第五节 互斥量概念,用法,死锁演示及解决
关于进程和线程管理,这里讲的只限于这里的案例,更多的关于进程线程管理,死锁管理,同步互斥关系,强烈建议先去学习《操作系统》。
一、互斥量(mutex)的概念
互斥:当一个进程或线程使用共享数据时,另一个线程或进程必须等待,当占用共享数据的线程或进程退出后,另一个线程或进程才允许去访问该共享数据。实现方式:操作时,某个线程用代码把共享数据锁住,操作数据,解锁;其他想操作数据的线程必须等待解锁,然后锁住,操作,解锁。
互斥量是个类对象,理解成一把锁,多个线程会尝试使用这个对象的lock()成员函数来加锁这把锁头,但是只有一个线程能锁定成功,如果没锁成功,那么该线程便会阻塞在这个地方;
二、互斥量(mutex)的用法
互斥量包含在一个头文件中,使用是需要包含该头文件#include
新建一个互斥量:std::mutex my_mutex;
2.1 lock(),unlock()
步骤:先lock(),操作数据,然后unlock();
lock()与unlock()必须成对使用。
#include<iostream>
#include<vector>
#include<thread>
#include<string>
#include<list>
#include<mutex>
using namespace std;
class A{
public:
//把收到的消息(玩家命令)放入到一个队列的线程入口函数
void inMsgRecvQueue(){
for(int i=0;i<10000;i++){//用数字模拟玩家发送来的命令
cout<<"inMsgRecvQueue()执行,插入一个元素 "<<i<<endl;
//
my_mutex.lock();//加锁
msgRecvQueue.push_back(i);//把命令放入队列当中
my_mutex.unlock();//解锁
//
}
}
bool MsgProcess(int &command){
my_mutex.lock();//加锁
if(!msgRecvQueue.empty()){
command=msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//取出后移除该元素
my_mutex.unlock();//解锁
//然后处理数据
return true;
}
my_mutex.unlock();//解锁
return false;
}
//从消息队列list中读取玩家命令的线程入口函数
void outMsgRecvQueue(){
int command=0;
for(int i=0;i<10000;i++){
//
bool result=MsgProcess(command);
if(result){
cout<<"outMsgRecvQueue执行,取出一个元素 "<<command<<endl;
//然后对数据进行处理
}
else
cout<<"outMsgRecvQueue执行,但是list已经空了 : "<<i<<endl;
}
cout<<"end "<<endl;
}
private:
std::list<int> msgRecvQueue;//在list中存放玩家发来的命令
std::mutex my_mutex;
};
int main(){
A myobj;
std::thread myInMsgObj(&A::outMsgRecvQueue,&myobj);//第二个参数是引用,作用与std::ref相同,保证是子线程中使用的是主线程中的同一个对象,但是主线程后面必须等待子线程完成
std::thread myOutMsgObj(&A::inMsgRecvQueue,&myobj);
myInMsgObj.join();
myOutMsgObj.join();
cout<<"主线程结束"<<endl;
return 0;
}
两个线程一次只能有一个能lock()成功,具体是那个lock()成功,由操作系统决定。
lock()与unlock()必须成对出现,为了防止lock后忘记unlock,C++引入了std::lock_guard的类模板,自动进行unlock;
2.2 std::lock_guard类模板:直接取代lock()和unlock()
一旦使用了lock_guard之后,便不能再使用lock和unlock;
使用方法:
std::lock_guard<std::mutex> lockguard(my_mutex);//lock_guard的类构造函数里执行了mutex::lock(),lock_guard的析构函数里执行了mutex::unlock()
lock_guard的类构造函数里执行了mutex::lock(),lock_guard的析构函数里执行了mutex::unlock();
例如:
bool MsgProcess(int &command){
std::lock_guard<std::mutex> lockguard(my_mutex);//lock_guard的类构造函数里执行了mutex::lock(),lock_guard的析构函数里执行了mutex::unlock()
//my_mutex.lock();//加锁
if(!msgRecvQueue.empty()){
command=msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//取出后移除该元素
//my_mutex.unlock();//解锁
//然后处理数据
return true;
}
//my_mutex.unlock();//解锁
return false;
}
注意:lock_guard()的unlock发生在作用域结束之前,对与其作用域要特别注意。对共享数据的访问一定要限制在lock_guard的作用域内。
三、死锁
张三:站在北京 等李四
李四:站在深圳 等张三
3.1 死锁概念:两个或以上的进程或线程,在本身都占有资源的情况下,请求其他线程或进程占有的资源,形成了一个环,导致每个线程或进程都无法进行执行的情况。
死锁的详细概念与形成条件,可以看《操作系统》;
死锁的必要条件:
- 互斥,所分配的资源一次只能为一个进程或线程所占有;
- 非剥夺,进程或线程所获得的资源在为使用完毕之前,不能被其他进程或线程强行夺走;
- 请求并保持,进程或线程已经至少拥有了一个资源,同时又请求其他进程或线程所占有的资源;
- 循环等待,互相等待对方所占有资源形成了一个循环等待链,每个进程或线程已占有的资源被链中的下一个进程或线程所请求;
C++中,例子:
两个线程A,B:
线程A执行的时候,锁住了金锁,把金锁锁住成功了,然后去lock银锁;突然出现了上下文切换,线程B开始执行了,线程B先锁银锁,成功了,然后线程B去lock金锁;此时死锁就发生了。此时线程A因为锁不了银锁而阻塞,线程B因为锁不了金锁而阻塞。
死锁的情况:
线程1:先锁住了互斥量my_mutex1,再锁住my_mutex2
my_mutex1.lock();//加锁
//****如果中间有其他代码
my_mutex2.lock();
msgRecvQueue.push_back(i);//把命令放入队列当中
my_mutex1.unlock();//解锁
my_mutex2.unlock();
线程2:先锁住了互斥量my_mutex2,再锁住my_mutex1
my_mutex2.lock();//加锁
my_mutex1.lock();
if(!msgRecvQueue.empty()){
command=msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//取出后移除该元素
my_mutex1.unlock();//解锁
my_mutex2.unlock();
//然后处理数据
return true;
}
my_mutex1.unlock();//解锁
my_mutex2.unlock();
此时便会发生死锁。
3.2 死锁的解决办法
这里只从当前这个代码的角度来解决死锁,其他更详细的的死锁解决方法看**《操作系统》**;
这里只需要保证线程一和线程二,加锁的顺序一致,就可以避免死锁。只要保证两个互斥量上锁的顺序一致就不会死锁。
3.3 std::lock()函数模板:处理多个互斥量时才出场
功能:一次锁住两个或两个以上的互斥量(至少两个,多了不限),但是一般使用情况比较少;
它不存在这种因为在多个线程中,因为锁的顺序导致死锁的风险问题。
std::lock(),如果要锁住的互斥量中有一个没锁住,就会阻塞,并且会把已经能锁住的互斥量解锁释放,等所有的互斥量都可以锁住之后,它才能往下走。
即要么两个互斥量都能锁住,要么都没锁住。
使用方法:
std::lock(my_mutex1,my_mutex2);//参数my_mutex1和my_mutex2顺序无所谓
//相关代码
my_mutex1.unlock();//解锁
my_mutex2.unlock();
3.4 std::lock_guard的std::adopt_lock参数
实际上类似于std::lock()与lock_guard()相结合的方法。
std::lock(my_mutex1,my_mutex2);
std::lock_guard<std::mutex> lockguard(my_mutex1,std::adopt_lock);//
std::lock_guard<std::mutex> lockguard(my_mutex2,std::adopt_lock);//
//相关代码
//my_mutex1.unlock();//解锁
//my_mutex2.unlock();
//
此时可以不用unlock();
std::adopt_lock参数是个结构体对象,起一个标记作用:表示这互斥量已经lock()过了,不需要在std::lock_guardstd::mutex的构造函数里对mutex对象进行lock了。
关于进程和线程管理,这里讲的只限于这里的案例,更多的关于进程线程管理,死锁管理,同步互斥关系,强烈建议先去学习《操作系统》。
本文介绍了互斥量(mutex)的概念及其在多线程环境中的使用方法,包括lock()、unlock()函数的应用与std::lock_guard类模板的使用。此外,还详细探讨了死锁现象的形成原因和预防措施。

被折叠的 条评论
为什么被折叠?



