C++11多线程(三) lock_guard unique_lock

2023-11-17

C++11多线程(三) lock_guard unique_lock

导读

上一节讲解了四种锁,但这四种锁并不符合C++ RAII的要求,因此C++11引入了lock_guard和unique_lock两个类模板。

RAII(Resource acquisition is initialization ):也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象。 ——百度百科

本篇相比之前内容更多,加入了源码的阅读和众多函数,请耐心阅读

Lock_guard

Lock_guard是一个类模板。

#include<mutex>
mutex m_lock;//创建锁
lock_guard<mutex> g_lock(m_lock);//使用g_lock绑定m_lock

在一个作用域内创建lock_guard对象时,会尝试获得锁(mutex::lock()),没有获得就像其他锁一样阻塞在原地。

在lock_guard的析构函数内会释放锁(mutex::unlock()),就不需要我们手动释放。

在实际编程中建议使用lock_guard,防止在获得锁后程序运行出现异常退出而导致锁死。

注意在我们使用lock_guard时就不要手动unlock()了,不然lock_guard的析构函数中解锁时就会报异常。

示例代码

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

class Test
{
public:
	Test();
	~Test();
	void f() {
		lock_guard<mutex> lg(lx);
		cout << "f()" << endl;
	}
	void f1() {
		lock_guard<mutex> lg(lx);
		cout << "f1()" << endl;
	}
private:

	mutex lx;
};

Test::Test()
{
}

Test::~Test()
{
};
int main() {
	Test T;
	while (1) {
		thread t1(&Test::f,  &T);
		thread t2(&Test::f1, &T);
		thread t3(&Test::f1, &T);
		thread t4(&Test::f1, &T);

		t1.join();
		t2.join();
		t3.join();
		t4.join();
	}
	return 0;
}

lock_guard<>的第二个参数


在今后的讲解中我都会尝试去翻阅一下源码和相关资料,毕竟源码的阅读对我们用于构造类和理解mutex也有很好的帮助

lock_guard在创建还可以传入第二个参数:adopt_lock;

这里给出lock_guard构造函数源码

explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
		{	// construct and lock
		_MyMutex.lock();
		}

	lock_guard(_Mutex& _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
		{	// construct but don't lock
		}

从两个构造函数源码中我们可以清晰的看到,传入adopt_lock是表明程序不需要自动帮我们对传给lock_gurad的锁进行lock()操作。因此,如果我们使用adopt_lock的话我们是需要进行手动上锁的。

_MyMutex是一个_Mutex的引用,_Mutex就是<>里的类型参数,无法理解的话你就把他当做 mutex。对源码还有疑问的小伙伴可以自行查阅相关资料,这里不做过多解释

void Test::f1() {
		lock_guard<mutex> lg(lx,adopt_lock);
		lx.lock();  //如果不调用lock(),在运行中就会报异常
		cout << "f1()" << endl;
	}

unique_lock

unique_lock也是一个类模板,和lock_guard是差不多的,也具有构造函数中加锁,析构函数解锁的能力,符合RAII原则。

但区别在于unique_lock提供了我们解锁能力(unique_lock::lock(),unique_lock::unlock() , 注意区分unique_lock对象的lock()方法,而不是mutex::lock(),虽然功能一样,但要理解调用对象的不同),可由程序员来进行手动解锁,这和lock_guard是不一样。unique_lock的极高灵活性提供了我们优化代码的能力,因为一旦lock_guard在创建获得锁之后必须要等到析构时才会释放锁,这样就造成其他线程在执行不涉及临界资源的代码时浪费了时间,大大降低效率。但这也代表着它的效率更低,占用内存更大(sizeof(lock_guard)是4,sizeof(unique_lock)是8,原因是多了一个_Owns变量,这是一个bool值,具体用途稍后介绍)。

class Test
{
public:
	Test();
	~Test();
	void f() {
		unique_lock<mutex> lg(lx);
		cout << "f()" << endl;
	}
	void f1() {
		unique_lock<mutex> lg(lx);
		cout << "f1()" << endl;
        lg.unlock(); //这里我们调用unlock()的话是不会像lock_guard那样报错的。
        lg.lock();
        //dosomething();
        lg.unlock();//别忘记unlock
	}
private:

	mutex lx;
};

Test::Test()
{
}

Test::~Test()
{
};
int main() {
	Test T;
	while (1) {
		thread t1(&Test::f,  &T);
		thread t2(&Test::f1, &T);
		thread t3(&Test::f1, &T);
		thread t4(&Test::f1, &T);

		t1.join();
		t2.join();
		t3.join();
		t4.join();
	}
	return 0;
}

unique_lock源码浅析(部分)

可能讲到这里,明白unique(唯一的)这个单词中文意思的同学还不能了解“唯一”在哪。

这里我们给出 =运算符 源码:

unique_lock& operator=(unique_lock&& _Other)//注意这里是右值
		{	// destructive copy
		if (this != &_Other)
			{	// different, move contents
			if (_Owns)
				_Pmtx->unlock();
			_Pmtx = _Other._Pmtx;
			_Owns = _Other._Owns;
			_Other._Pmtx = 0;
			_Other._Owns = false;
			}
		return (*this);
		}

_Owns就是刚刚提到使 unique_lock<>占用空间更大的bool变量,用以表示是否持有锁

__Pmtx是一个_Mutex类型的指针

_Mutex就是<>里的类型参数,无法理解的话你就把他当成mutex类型

从源码里我们看出,一旦用 =运算将一个 unique_lock 赋值给另一个unique_lock后,原先的锁就会被释放,这里和unique_ptr有点像。值得注意的是,之前我们提过的四种mutex变量都没有重载等号运算符和拷贝构造函数,因为他们都继承于_Mutex_base这个类。

unique_lock的第二个参数

unique_lock 和 lock_guard 一样 可以传入第二个参数:

adopt_lock

表明我们需要手动上锁,具体看之前代码

这里给出源码

unique_lock(_Mutex& _Mtx, adopt_lock_t)
		: _Pmtx(&_Mtx), _Owns(true)
		{	// construct and assume already locked
		}

还有其他参数也可以传入

defer_lock

初始化时并不锁住lx

void f1() {
		unique_lock<mutex> lg(lx,defer_lock);
        dosomething();
	}

这里给出源码

unique_lock(_Mutex& _Mtx, defer_lock_t) _NOEXCEPT
		: _Pmtx(&_Mtx), _Owns(false)
		{	// construct but don't lock
		}

try_to_lock

尝试去获得锁

这里给出源码

unique_lock(_Mutex& _Mtx, try_to_lock_t)
		: _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock())
		{	// construct and try to lock
		}

这里可以从初始化列表看出_Owns是由传入的 _Mtx来决定值,之前也提到过try_lock(),有疑问记得回顾

unique_lock常用函数以及一些补充

bool owns_lock(): 返回_Owns 也就是是否用有锁

mutex* release():返回类中的锁指针,代表调用这个方法的unique_lock和传给它的mutex就没有任何关系了,需要我们之后手动进行mutex::lock()以及

​ mutex::unlock()操作。

operatpr bool(): 返回_Owns,和owns_lock()一样

unique_lock(unique_lock&& _Other) :传入另一个unique_lock的构造函数,注意这是一个破坏性拷贝,他会使传进来的 _Other恢复默认状态。

后面讲解较少的这部分是不怎么常用的,其实unique还有好多方法和第二参数,但因使用较少就不多做讲述,防止记忆混乱

死锁

死锁的概念

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

——百度百科

死锁产生的4个必要条件

  1. 互斥: 资源单独访问。

  2. 占有且等待: 本身占有资源但还未满足启动条件,保持当前资源并等待其他资源。

  3. 不可抢占: 无法获得还未释放的资源。

  4. 循环等待: 每个进程都在等待别人手上的资源,并且形成回路。
    当以上四个条件均满足,就会造成死锁,发生死锁的进程无法进行,所持有的资源也无法释放。

死锁情况非常浪费系统资源和影响计算机的使用性能的,因此我们在多线程编程中也需要注意防止死锁。

有关扩展自行查找操作系统系列知识

C++11中防止死锁的方法

std::lock()函数:

注意这里的lock()函数并不是mutex::lock(),也不是unique::lock()。

//假设我们某个线程需要两个资源A,B 我们用lock_A和lock_B来锁住他们。
//不用①lock_A::lock();②lock_B::lock();的原因是可能我们在某个线程执行①的时候,
//另一个线程执行了②,就会发生死锁
//我们可以使用std::lock()函数来保证同时拿到两种资源。
void f1(){
    lock(lock_A,lock_B);
    lock_guard<mutex> lockA(lock_A, adopt_lock);  //这里就可以使用adopt_lock了 不给出原因 ,自行思考加深印象
	lock_guard<mutex> lockB(lock_B, adopt_lock);

}

本文仅用于本人学习过程中类似于笔记的记录,如有疑问和错误请在评论区指出。
请勿转载,谢谢您的观看。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11多线程(三) lock_guard unique_lock 的相关文章

  • 如何使用 Qtimer 添加 1 秒延迟

    我目前有一个方法如下 void SomeMethod int a Delay for one sec timer gt start 1000 After one sec SomeOtherFunction a 这个方法实际上是一个附加到信号
  • 类型转换 sockaddr 结构

    我正在尝试学习网络编程 并在这个过程中学习C 我对结构感到困惑sockaddr这是一个通用地址 并且sockaddr in 我的书里是这么说的 因此 我们可以填写 sockaddr in 的字段 然后强制转换 a 指向 它指向 指向 soc
  • 如何查找boost运行时版本

    我正在编写一个使用 boost 的 C 库 在这个库中 我想包含有关用于编译我的库的二进制版本的 boost 版本的信息 我可以使用宏BOOST VERSION这很好 我还想确定哪个是 boost 的运行时版本 以便我可以与用于编译我的库的
  • 加权 Voronoi 的 CGAL 2D APOLLONIUS 图 - 如何生成和获取面和顶点?

    我正在尝试根据阿波罗尼乌斯图生成加权沃罗诺伊 我正在使用 CGAL 库 我找不到如何从 apollonius 获取面和顶点的好例子 我有以下类型定义 typedef double NT typedef CGAL Cartesian lt N
  • 将数组从 C# 编组到 C++ 并返回:PInvokeStackImbalance

    我有一个 C 函数 我想从 C 访问它 问题是我不断收到 PInvokeStackImbalance 异常 但我不知道为什么 当检查异常被关闭时 一切都运行良好并且符合预期 我的 C 函数的签名是 extern C double solve
  • 无法使用 Unity 函数在 Visual Studio Code 中获得完整的 Intellisense

    好吧 我知道这个问题已经被问过并回答过很多次了 但我花了大约 3 天的时间试图解决这个问题 但到目前为止我所做的一切都没有奏效 我基本上在 Visual Studio Code 中有部分智能感知 也就是说 它似乎只识别 Unity 类和变量
  • Code First - 实体框架 - 如何公开外键

    我有以下数据对象 public class Customer System Data Entity ModelConfiguration EntityTypeConfiguration
  • 在 C 程序中追踪数组越界访问/写入的推荐方法

    考虑用 C 语言编写一些不太明显的算法的实现 例如 让它成为递归快速排序 我在 K N King 的 C 编程 现代方法 第二版 书中找到了它 可以从here http knking com books c2 programs qsort
  • 使用 Thread.Sleep() 时,异步编程如何与线程一起工作?

    假设 前言 在之前的问题中 我们注意到Thread Sleep阻塞线程参见 什么时候使用Task Delay 什么时候使用Thread Sleep https stackoverflow com questions 20082221 whe
  • Xamarin 无法从异步获取实例

    我编写了一个通过蓝牙连接到 ESP32 的 Xamarin Forms 应用程序 现在我想从 MainPage xaml 页面的 CustomControl JoystickControl 获取值 我已经这样尝试过了 MainPage xa
  • 未定义条件编译符号

    我无法让 Visual Studio 按照我的预期运行 我创建了 2 个配置文件 一个定义了符号 FOO 另一个定义了符号 BAR 我有这个代码 static class MyClass if FOO public static strin
  • 如何使用 Caliburn.Micro MVVM 将焦点设置到控件

    我有一个表单 我想在发生某些用户操作时将焦点设置到文本框 我知道 MVVM 的处理方式是绑定到 VM 属性 但是 TextBox 没有允许这种情况发生的属性 从虚拟机设置焦点的最佳方法是什么 我创建了一个 IResult 实现 可以很好地实
  • 专家 C#/.Net/WPF 开发人员应该了解哪些知识? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 在 C# 中加密并在 Flex 中解密

    我需要解密 Flex 中的一些数据 这些数据是用 C 加密并写入文件的 为了简单起见 我选择使用 as3crypto As3 库和 Bruce Schneier C 库 AS3 as3加密链接 http code google com p
  • 我可以在C中直接比较int和size_t吗?

    我可以比较一个int and a size t像这样的变量 int i 1 size t y 2 if i y Do something 或者我必须输入其中之一 只要满足以下条件 它就是安全的int为零或正数 如果它是负数 并且size t
  • 修改公共属性的访问修饰符是否是重大更改?

    如果我将公共属性的 setter 的访问修饰符从私有更改为公共 是否会导致引用它的其他程序集发生任何重大更改 UPDATE 这个问题是我 2012 年 1 月博客的主题 https ericlippert com 2012 01 09 ev
  • 如何从标准输入读取一行,阻塞直到找到换行符?

    我试图从命令行的标准输入一次读取任意长度的一行 我不确定是否能够包含 GNU readline 并且更喜欢使用库函数 我读过的文档表明getline应该可以工作 但在我的实验中它不会阻塞 我的示例程序 include
  • 如果未返回,则在一段时间后终止线程

    我有一个线程从网络或串行端口获取一些数据 如果 5 秒内没有收到数据 则线程必须终止 或返回 false 换句话说 如果线程运行时间超过 5 秒 则必须停止 我用 C 编写 但任何 NET 语言都可以 有两种方法 1 封装超时 从网络或串行
  • 使用 wmi 获取活动会话(Win32_LogonSession 还返回非活动/旧会话)

    有没有办法只显示 wmi 的活动会话 问题是 Win32 LogonSession 还显示不活动 断开连接的会话 ManagementScope scope new ManagementScope ManagementPath Defaul
  • 你将如何开始自动化我的工作? - 第2部分

    后续这个问题 https stackoverflow com questions 2796128 how would you start automating my job 在经历了第一波进货 9 小时的复制 粘贴 后 我现在相信我已经满足

随机推荐

  • 使用ESP定律_手工脱壳

    ESP定律脱壳一般的加壳软件在执行时 首先要初始化 保存环境 保存各个寄存器的值 一般利用PUSHAD 相当于把所有寄存器都压栈 当加壳程序的外壳执行完毕以后 再来恢复各个寄存器的内容 通过跨区段的转移来跳到程序的OEP来执行原程序 简单点
  • lr(1)分析法 算数表达式 c语言,编译原理及技术期末考试复习试题整理

    2 1 考虑文法G S 其产生式如下 S L a L L S S 1 试指出此文法的终结符号 非终结符号 终结符号为 a 非终结符号为 S L 开始符号为 S 2 给出下列各句子的分析树 a a a a a a a a a a 3 构造下列
  • Ubuntu20.04 开机无法进入登陆界面,一致转圈圈解决方案

    昨天把一个新的主机装了显卡驱动 cudnn没装完就关机走人了 今天早上一开机发现显示了这个 我没拍照片 这里盗用别的博主的照片了 搜了一下 本着能省则省的原则先从最简单的情况试起 怀疑是Lightdm出了问题 借用一下博主原话 是安装了li
  • 机器视觉开源代码集合

    机器视觉开源代码集合 一 特征提取Feature Extraction SIFT 1 Demo program SIFT Library VLFeat PCA SIFT 2 Project Affine SIFT 3 Project SUR
  • (struts2学习篇)struts2文件上传

    第一步 编写相关相关文件上传Action public class UploadFileAction extends ActionSupport private static final long serialVersionUID 1L 相
  • Hive千亿级数据倾斜解决方案

    数据倾斜问题剖析 数据倾斜是分布式系统不可避免的问题 任何分布式系统都有几率发生数据倾斜 但有些小伙伴在平时工作中感知不是很明显 这里要注意本篇文章的标题 千亿级数据 为什么说千亿级 因为如果一个任务的数据量只有几百万 它即使发生了数据倾斜
  • 十种经典运放电路分析

    转载十一种经典运放电路分析 本文章为转载文章 只是为以后方便查阅 如有侵权 请联系本人 1 反向放大器 图一运放的同向端接地 0V 反向端和同向端虚短 所以也是0V 反向输入端输入电阻很高 虚断 几乎没有电流注入和流出 那么R1和R2相当于
  • python解决Net Frameword匹配问题及Failed building wheel for XXX

    文章目录 1 背景 2 错误描述 2 1 错误关键语句 1 2 2 错误关键语句 2 2 3 错误关键语句 3 3 原因 4 解决问题 5 总结 6 参考链接 1 背景 计划使用NI veristand的python依赖包 但是在安装的过程
  • 算法设计与分析--求最大子段和问题(蛮力法、分治法、动态规划法) C++实现

    算法设计与分析 求最大子段和问题 问题描述 给定由n个整数组成的序列 a1 a2 an 求该序列形如 的子段和的最大值 当所有整数均为负整数时 其最大子段和为0 利用蛮力法求解 int maxSum int a int n int maxS
  • openGauss和oracle的上下翻命令和自动补全

    openGauss的gsql需要加参数 r 才能支持上下翻命令和自动补全 gsql d postgres p 15400 r oracle的sqlplus也不支持上下翻命令和自动补全 使用rlwrap可以实现上下翻命令 但是还是不能实现自动
  • Could not load dynamic library ‘cudart64_110.dll‘; dlerror: cudart64_110.dll not found

    Could not load dynamic library cudart64 110 dll dlerror cudart64 110 dll not found 报错如下 解决方法1 不推荐 对后续使用影响大 解决方法2 验证代码 输出
  • python拼接两个或者多个视频文件

    拼接不同分辨率的视频文件 import os import linecache 读取指定路径下的所有文件并放入到列表中 root workspace videos codec videos codec evp test h264 file
  • 【毕设选题】小红书数据分析与可视化

    文章目录 0 前言 1 课题背景 2 数据库依赖 导入依赖包 3 分析服饰行业笔记数据趋势数据 3 1数据一览 3 2 可视化分析 3 3 可视化分析 4 分析服饰行业内容关键词数据 4 1 数据一览 4 2 可视化分析 5 分析服饰行业品
  • VUE map area coords自适应

  • java异步调用方法

    1 CompletableFuture 使用原生的CompletableFuture实现异步操作 加上对lambda的支持 可以说实现异步任务已经发挥到了极致 Test public void test2 throws Exception
  • 软件测试基础学习

    1 软件和软件测试 1 1 软件 软件组成 程序 数据 文档 软件的分类 按层次划分 系统软件 应用软件 按组织划分 商业软件 开源软件 按结构划分 单机软件 分布式软件 1 2缺陷的由来 软件缺陷的由来 Bug Defect 所有不满足需
  • 使用python写一个可以帮我混淆加密Lua脚本的程序

    首先 我们需要了解一下混淆加密的概念 混淆加密是指将程序代码进行特殊的处理 使其难以被人类理解或反编译 这有助于保护程序的版权和商业机密 对于使用 Python 编写的程序来说 我们可以使用第三方库 pyminifier 来混淆加密 Pyt
  • 【详解】MySQL索引的基本操作,索引(主键索引,普通索引,组合索引,唯一索引)

    索引底层原理 详解 面试必问 MySQL索引底层原理 基于B Tree CodingLJ CSDN博客 前言 索引是什么 索引是一种单独的 物理的对数据库表中一列或多列的值进行排序的一种存储结构 它是某个表中一列或若干列值的集合和相应的指向
  • ASP中Utf-8与Gb2312编码转换乱码问题的解决方法

    asp程序在同一个站点中 如果有utf 8编码的程序 又有gb2312编码的程序时 在浏览utf 8编码的页面后 再浏览当前网站gb2312的页面 gb2312编码的页面就会出现乱码 出现这样的问题是当你浏览utf 8编码的时候 服务器默认
  • C++11多线程(三) lock_guard unique_lock

    文章目录 C 11多线程 三 lock guard unique lock 导读 Lock guard 示例代码 lock guard lt gt 的第二个参数 unique lock unique lock源码浅析 部分 unique l