首页 > 其他分享 >互斥量概念、用法、死锁演示及解决详解

互斥量概念、用法、死锁演示及解决详解

时间:2023-08-17 14:34:33浏览次数:37  
标签:std 获取 lock 互斥 死锁 详解 线程

互斥量概念、用法、死锁演示及解决详解

视频:https://www.bilibili.com/video/BV1Yb411L7ak?p=7&vd_source=4c026d3f6b5fac18846e94bc649fd7d0
参考文章:https://blog.csdn.net/qq_38231713/article/details/106091902

互斥量(mutex)

如果想深入了解可以具体看一下操作系统互斥量的讲解

  • 互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
  • 互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。

互斥量的用法

lock()、unlock()

#include<iostream>
#include<thread>
#include<vector>
#include<list>
#include<mutex>
using namespace std;

class A {
public:
	void inMsgqueue() {
		for (int i = 0; i < 10000; i++) {
			cout << "inMsgqueue()执行,插入元素" << i << endl;
			my_mutex.lock();
			msgqueue.push_back(i);
			my_mutex.unlock();
		}
	}
	void outMsgqueue() {
		for (int i = 0; i < 10000; i++) {
			if (!msgqueue.empty()) {
				int command = msgqueue.front();
				my_mutex.lock();
				msgqueue.pop_front();
				my_mutex.unlock();
			}
			else {
				cout << "outMsgqueue()执行,但为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}
private:
	std::list<int>msgqueue;
	std::mutex my_mutex;
};

int main() {
	
	A myobject;
	thread myoutmsgobj(&A::outMsgqueue, &myobject);
	thread myinmsgobj(&A::inMsgqueue, &myobject);
	myinmsgobj.join();
	myoutmsgobj.join();
	cout << "主线程开始" << endl;
	return 0;
}

这样一个时间段内就只能一个数据存或者读

lock_guard类用法

  • lock_guard sbguard(myMutex);取代lock()和unlock()
  • lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()
    img

死锁

死锁的条件

  • 互斥条件(Mutual Exclusion):至少有一个资源只能被一个进程(线程)同时占用,即该资源一次只能服务一个请求。这意味着当一个进程(线程)占用了该资源后,其他进程(线程)必须等待该资源释放。

  • 持有并等待条件(Hold and Wait):一个进程(线程)在等待其他进程(线程)所持有的资源时,持有自己已占用的资源不释放。换句话说,进程(线程)在等待资源的同时,继续持有已占用的资源。

  • 不可抢占条件(No Preemption):已分配给进程(线程)的资源不能被强制性地抢占,只能在进程(线程)使用完后自愿释放。

  • 循环等待条件(Circular Wait):在系统中存在一个进程(线程)资源的循环链,即每个进程(线程)都在等待下一个进程(线程)所持有的资源。

死锁例子

  • 进程A获取资源X。
  • 进程B获取资源Y。
  • 进程A尝试获取资源Y,但由于被进程B占用,所以A被阻塞。
  • 进程B尝试获取资源X,但由于被进程A占用,所以B被阻塞。
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void func1() {
    std::lock_guard<std::mutex> lock1(mutex1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 为了让两个线程有足够的时间相互等待
    std::lock_guard<std::mutex> lock2(mutex2);
    std::cout << "Thread 1 executed successfully" << std::endl;
}

void func2() {
    std::lock_guard<std::mutex> lock2(mutex2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 为了让两个线程有足够的时间相互等待
    std::lock_guard<std::mutex> lock1(mutex1);
    std::cout << "Thread 2 executed successfully" << std::endl;
}

int main() {
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
    
    std::cout << "Main thread executed successfully" << std::endl;
    return 0;
}

死锁的解决方案

死锁四个条件破坏一个即可

视频老师只讲了只要保证多个互斥量上锁的顺序一样就不会造成死锁。但其实正规来说是破坏一个条件即可

std::lock()函数模板

当多个线程需要同时获取多个互斥量的锁时,如果不谨慎处理,可能会产生死锁的情况。而std::lock()函数模板的作用就是帮助我们避免死锁。

通俗地说,std::lock()函数模板可以同时获取多个互斥量的锁,或者不获取任何锁。它的作用就像是一个协调者,帮助多个线程按照某种顺序获取锁,以避免它们之间相互等待的死锁情况。

假设有两个线程A和B,它们分别需要获取互斥量X和Y的锁。如果A先获取了锁X,而B先获取了锁Y,那么它们就会相互等待对方释放锁,导致死锁。但是,如果我们使用std::lock()函数模板来同时获取锁X和锁Y,它会按照某种顺序获取锁,确保两个线程都能成功获取所需的锁。如果无法同时获取所有锁,std::lock()函数会自动释放已经获取的锁,以避免死锁的发生。
img
上述图片展示了std::lock()的用法,可以替代某些语句

std::lock_guard的std::adopt_lock参数

在使用std::lock_guard时,可以通过将std::adopt_lock作为额外的参数传递给它来指示它应该"接管"已经获得的互斥量的所有权。

通俗地说,当我们使用std::lock_guard时,它会在构造函数中自动获取互斥量的锁,并在析构函数中自动释放锁。默认情况下,std::lock_guard会尝试在构造函数中获取锁。然而,有时候我们已经手动获取了互斥量的锁,此时我们不希望std::lock_guard再次获取锁,而是直接接管已经获得的锁的所有权。这时候就可以使用std::adopt_lock参数。

简单来说,std::adopt_lock的作用是告诉std::lock_guard,互斥量的锁已经被手动获取,不需要再重新获取。std::lock_guard接收到这个参数后,会假定互斥量的锁已经被获取,并在析构函数中自动释放锁。

这样做的好处是,我们可以在代码的不同部分使用不同的方式获取锁,而不担心重复获取锁导致死锁或其他问题。通过使用std::adopt_lock参数,我们可以告诉std::lock_guard使用已经手动获取的锁,确保代码的正确性和安全性。
img
就好比这份代码,已经用lock给两个互斥量上锁了,不能再用lock_guard给他上锁了,只能用它的解锁,std::adopt_lock是个结构体对象,起一个标记作用;作用就是表示这个互斥量已经lock();

标签:std,获取,lock,互斥,死锁,详解,线程
From: https://www.cnblogs.com/yjydxuexi/p/17568336.html

相关文章

  • Gson与FastJson详解
    Gson与FastJson详解Java与JSON做什么?将Java中的对象快速的转换为JSON格式的字符串.将JSON格式的字符串,转换为Java的对象.Gson将对象转换为JSON字符串转换JSON字符串的步骤:引入JAR包在需要转换JSON字符串的位置编写如下代码即可:Stringjson=newGson().toJSON(要转换的对象......
  • toggleClass详解
    toggleClass()是一个jQuery方法,用于在元素之间切换一个或多个类。该方法的语法如下:$(selector).toggleClass(class1,class2,...)selector:表示要切换类的元素选择器。class1,class2,...:要切换的一个或多个类名。该方法的作用是,在被选元素上添加指定的类,如果元素已经有......
  • ConcurrentHashMap 源码详解
    ConcurrentHashMap是Java提供的一个并发散列映射实现,它允许多个线程同时读写而不需要同步整个数据结构。它是线程安全的,并且相比于其他线程安全的Map实现(如Collections.synchronizedMap或Hashtable),它提供了更高的并发性能。以下是ConcurrentHashMap的一些核心特性和相应......
  • DataBinding开始使用以及布局详解
    DataBinding开始使用了解如何为您的开发环境支持使用DataBinding,包括在AndroidStudio中支持数据绑定代码。DataBinding提供了灵活性和广泛的兼容性-它是一个支持库,因此您可以将其用于运行Android4.0(API14级)或更高版本的设备。我们建议在您的项目中使用最新的AndroidGradle插件......
  • 鸿蒙入门开发教程:一文带你详解工具箱元服务的开发流程
    鸿蒙入门开发教程:一文带你详解工具箱元服务的开发流程一,基本概念元服务(原名原子化服务)是一种基于HarmonyOSAPI的全新服务提供方式,以HarmonyOS万能卡片等多种呈现形态,向用户提供更轻量化的服务。具有即用即走、信息外显、服务直达的特性。万能卡片(简称卡片)是一种界面展示形式,可......
  • [BitSail] Connector开发详解系列三:SourceReader
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群SourceConnector本文将主要介绍负责数据读取的组件SourceReader:SourceReader每个SourceReader都在独立的线程中执行,只要我们保证SourceSplitCoordinator分配给不同SourceReader的切片没有交集,在S......
  • [BitSail] Connector开发详解系列三:SourceReader
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群SourceConnector本文将主要介绍负责数据读取的组件SourceReader:SourceReader每个SourceReader都在独立的线程中执行,只要我们保证SourceSplitCoordinator分配给不同SourceReader的切......
  • Java中Date方法详解
    先进行专栏介绍本专栏是自己学Java的旅途,纯手敲的代码,自己跟着黑马课程学习的,并加入一些自己的理解,对代码和笔记进行适当修改。希望能对大家能有所帮助,同时也是请大家对我进行监督,对我写的代码进行建议,互相学习。Date方法Date类是用于表示日期和时间的类。它提供了一系列的方......
  • ulimit -a详解
    参数描述corefilesizecore文件的最大值为100blocks,datasegsize进程的数据段可以任意大filesize文件可以任意大pendingsignals最多有2047个待处理的信号maxlockedmemory一个任务锁住的物理内存的最大值为32kBmaxmemorysize一个任务的常驻物理内存的最大值......
  • 记一次MySQL死锁问题排查
    事情的起因:我司有一款应用处于新旧系统切换阶段,新旧服务同时穿插运行,新服务不断迭代的同时来不断下线旧服务,其中有一个编辑客户信息的功能因为工作量太大,所以其中一部分内容是通过RPC的方式调用新服务的API进行保存的,然后在出现了一个神奇的问题,RPC接口频繁超时,于是我对RPC接口......