C++标准库中的锁

news2024/12/29 10:47:25

C++标准库中的锁

std::mutex.lock是我们在C++中比较常见的锁,我们使用std::mutex.lock方法时,同时需要考虑何时使用std:mutex.unlock方法去解锁。如果在复杂的多线程情况下,加锁、解锁的时机很难把握,也不好实现。

RAII原则是所有的资源都必须有管理对象,而资源的申请操作在管理对象的构造函数中进行,而资源的回收则在管理对象的析构函数中进行

C++新标准提供了lock_guard, unique_lock, shared_lock, 和 scoped_lock四种锁,用于各种复杂情况。这四种锁都是满足RAII风格。

lock_guard

  1. lock_guard:这是C++11中一个简单的 RAII(Resource Acquisition Is Initialization)风格的锁,用于在作用域内自动管理互斥量的锁定和解锁。当 lock_guard 对象被创建时,它会自动锁定互斥量,当对象离开作用域时,它会自动解锁互斥量。lock_guard 不支持手动锁定和解锁,也不支持条件变量。
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;
int shared_data = 0;

void increment() {
	std::lock_guard<std::mutex> lock(mtx);
	++shared_data;
	std::cout << "Incremented shared_data: " << shared_data << std::endl;
}
int main() {
	std::thread t1(increment);
	std::thread t2(increment);

	t1.join();
	t2.join();

	return 0;
}

运行结果:
在这里插入图片描述

unique_lock

unique_lock:这是C++11中一个更灵活的锁,它允许手动锁定解锁互斥量,以及与条件变量一起使用(是lock_guard的进阶版)。与 lock_guard 类似,unique_lock 也是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁互斥量。unique_lock 还支持延迟锁定、尝试锁定和可转移的锁所有权。

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
int shared_data = 0;
bool ready = false;

void increment() {
	std::unique_lock<std::mutex> lock(mtx);
	cv.wait(lock, [] { return ready; });
	++shared_data;
	std::cout << "Incremented shared_data: " << shared_data << std::endl;
}

void set_ready() {
	std::unique_lock<std::mutex> lock(mtx);
	std::cout << "set_ready finish!" << std::endl;
	ready = true;
	cv.notify_all();
}

int main() {
	std::thread t1(increment);
	std::thread t2(increment);
	std::thread t3(set_ready);

	t1.join();
	t2.join();
	t3.join();

	return 0;
}

运行结果:
在这里插入图片描述

shared_lock

  1. shared_lock:C++14引入的锁,这是一个用于共享互斥量(如 std::shared_mutexstd::shared_timed_mutex)的锁,允许多个线程同时读取共享数据,但在写入数据时仍然保证互斥。shared_lock 也是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁共享互斥量。shared_lock 支持手动锁定和解锁,以及尝试锁定。
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <thread>

std::shared_mutex sh_mtx;
int shared_data = 0;

void read_data() {
	std::shared_lock<std::shared_mutex> lock(sh_mtx);
	std::cout << "Read shared_data: " << shared_data << std::endl;
}

void write_data() {
	std::unique_lock<std::shared_mutex> lock(sh_mtx);
	++shared_data;
	std::cout << "Incremented shared_data: " << shared_data << std::endl;
}

int main() {
	std::thread t1(read_data);
	std::thread t2(write_data);
	std::thread t3(read_data);

	t1.join();
	t2.join();
	t3.join();

	return 0;
}

运行结果:
在这里插入图片描述

scoped_lock

scoped_lock:这是 C++17 引入的一个新锁,用于同时锁定多个互斥量,以避免死锁。scoped_lock 是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁所有互斥量。scoped_lock 不支持手动锁定和解锁,也不支持条件变量。它的主要用途是在需要同时锁定多个互斥量时提供简单且安全的解决方案。

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

std::mutex mtx1;
std::mutex mtx2;
int shared_data1 = 0;
int shared_data2 = 0;

void increment_both() {
    std::scoped_lock lock(mtx1, mtx2);
    ++shared_data1;
    ++shared_data2;
    std::cout << "Incremented shared_data1: " << shared_data1 << ", shared_data2: " << shared_data2 << std::endl;
}

int main() {
    std::thread t1(increment_both);
    std::thread t2(increment_both);

    t1.join();
    t2.join();

    return 0;
}

互斥量

recursive_mutex

使用场景:一个类的不同成员函数之间,存在相互调用的情况, 如果这样的成员函数作为线程的入口函数时,就会出现在成员函数 func1()中对某个互斥量上锁,并且, func1()中调用了成员函数 func2() ,实际上 func2()为了保护成员数据,func2()内部也对同一个互斥量上锁。 在我们对 std::mutex 的使用经验中, 这样的情况,必定会导致未定义的行为,从而导致死锁的产生。

C++标准库为此提供了 std::recursive_mutex 互斥量, 它在具备 std::mutex 的功能之上, 还可以可以支持上面描述的 “对同一个互斥量进行嵌套上锁” 的能力。

std::recursive_mutex是C++标准库中的一个互斥锁类型,允许同一个线程多次锁定同一个互斥锁,而不会导致死锁。每次锁定互斥锁时,都会增加一个计数器。当解锁互斥锁时,计数器减少。只有当计数器为零时,互斥锁才会被释放,其他线程才能锁定它。

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

std::recursive_mutex mtx;

void print_hello(int n) {
	std::lock_guard<std::recursive_mutex> lock(mtx);
	if (n > 0) {
		std::cout << "Hello, ";
		print_hello(n - 1);
	}
	else {
		std::cout << "world!" << std::endl;
	}
}

int main() {
	std::thread t1(print_hello, 5);
	std::thread t2(print_hello, 3);

	t1.join();
	t2.join();

	return 0;
}

运行结果:

在这里插入图片描述

递归运行,同一个线程同一个互斥量的情况可以避免死锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/530484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++中this指针的使用

首先&#xff0c;我们都知道类的成员函数可以访问类的数据&#xff0c;那么成员函数如何知道哪个对象的数据成员要被操作呢&#xff1f; 原因在于每个对象都拥有一个指针&#xff1a;this指针&#xff0c;每一个对象都能通过this指针来访问自己的地址。 this 指针是所有成员函…

NOSQL——redis的安装,配置与简单操作

1.缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬…

Git简易教程

安装与配置 教程链接 代码上传到远程仓库 workspace&#xff1a;当前所在的工作目录 stagingarea&#xff1a;修改暂存区 loacl repository&#xff1a;本地仓库 remote respository &#xff1a;远程仓库 我们首先在workspace目录编写代码、修改文件&#xff0c;然后需要使用…

三十五、nacos注册中心集群搭建、网关、过滤器

1、nacos注册中心集群的搭建 要想搭建nacos集群模式&#xff0c;这些集群中的每台nacos服务&#xff0c;都必须连接同一个数据库。因为我们的nacos都放在同一台主机上&#xff0c;所以我们必须为每台nacos起不同的端口号。 1.1修改nacos端口号 8849 8850 8851 E:\software\naco…

2023/5/15总结

Java基础&#xff08;4&#xff09; 标准类制作 成员变量使用private修饰构造方法&#xff1a;提供一个无参构造方法&#xff0c;提供一个带多个参数的构造方法 成员方法&#xff1a;提供每一个成员变量对应的setXxx()/getXxx(),提供一个显示对象信息show()创建对象并为其成员…

服务端渲染

服务端渲染 和 前后端分离&#xff01; 渲染 什么是渲染呢 ? 其实很简单, 就是把数据反应在页面上&#xff0c;说白了, 就是利用 js 的语法, 把某些数据组装成 html 结构的样子, 放在页面上展示。 举个例子 : 1. 准备一段 html 结构 <h1>hello world</h1> <di…

Android之 Service服务详解

一 四大组件 1.1 Activity组件&#xff0c;它一个单独的窗口&#xff0c;程序流程都必须在Activity中运行&#xff0c;所有它是最基本的模块。 1.2 service组件&#xff0c;用于在后台完成用户指定的操作。 1.3 content provider组件&#xff0c;会为所有的应用准备一个内容…

零知识证明:重要构造

文章目录 ZKP for NPBlums ZK PoK for HCCompletenessSoundnessZero KnowledgeWI of n-parallelized versionsProof of KnowledgeSpecial Soundness Constant Round ZKPFLS ParadigmGK Paradigm 在 零知识证明&#xff1a;安全定义 中介绍了 ZKP 的一些安全性定义&#xff0c;…

TFT驱动ST7789使用总结

最近在使用一款TFT驱动芯片ST7789&#xff0c;在阅读芯片数据手册和液晶屏数据手册时&#xff0c;发现总是对不上&#xff0c;芯片手册中&#xff0c;有好几个引脚&#xff0c;一会儿是这个作用&#xff0c;一会儿又变成另一种作用&#xff0c;实在是让人感到混淆。网上找了好久…

第三十六天学习记录:C语言进阶:指针详解Ⅳ

指向函数指针数组的指针 指向函数指针数组的指针是一个指针&#xff0c;指针指向一个数组&#xff0c;数组的元素都是函数指针。 int(*pfArr[4])(int, int);//pfArr是一个数组-函数指针的数组int(*(*ppfArr)[4])(int, int) &pfArr;//ppfArr是一个数组指针&#xff0c;指针…

网络进阶学习:重要网络协议(tcp协议,udp协议,http协议)

重要网络协议&#xff08;tcp协议&#xff0c;udp协议&#xff0c;http协议&#xff09; 网络协议是什么?TCP协议UDP协议HTTP协议TCP与UDP的职能区别⭐TCP职能⭐就TCP原理层面说应该分为三部分建立连接数据传输断开连接 ⭐UDP职能⭐就UDP原理层面说应该分为三部分发送数据数据…

单链表(增、删、查、改)的详细介绍 必看!!!

文章目录 链表介绍单链表初始化单链表打印增加节点单链表的头插单链表的尾插在给定位置之后插入在给定位置之前插入 删除节点单链表的头删单链表的尾删删除给定位置之后的节点删除给定位置处的节点 查找节点修改节点单链表销毁 链表介绍 链表是一种物理存储单元上非连续、非顺序…

【Mybatis】如何实现ORM映射-二

唠嗑部分 上篇文章我们说了Mybatis基本的CRUD操作及工具类的封装&#xff0c;相关文章&#xff1a; 【Mybatis】简单入门及工具类封装-一 大家都知道&#xff0c;Mybatis是半自动化的ORM框架&#xff0c;那么它到底是如何帮我们完成ORM映射的呢&#xff1f; 这就是本篇文章和…

SpringBoot整合Dubbo+Zookeeper

第一步、使用IDE创建一个SpringBoot项目 第二步、启动一个Zookeeper服务&#xff08;如果是第一次安装且没有配置zoo.cfg&#xff0c;此时会报出zookeeper服务器会提示缺少zoo.cfg文件&#xff09; 在zookeeper安装目录下的conf文件夹内有一个名为zoo_sample.cfg的配置文件&a…

[深度学习思想] ControlNet 工作原理

Stable Diffusion (2021 https://arxiv.org/abs/2112.10752) 带领了vision领域&#xff0c;具体是AI绘画领域达到了一个新高度。但是可控性成为使用的一个大瓶颈。Controlnet (2023 https://arxiv.org/abs/2302.05543) 提出一个深度学习模型框架&#xff0c;很好解决这个问题&a…

Liunx基础命令 - touch命令

touch命令 – 创建空文件与修改时间戳 touch命令的功能是用于创建空文件与修改时间戳。如果文件不存在&#xff0c;则会创建出一个空内容的文本文件&#xff1b;如果文件已经存在&#xff0c;则会对文件的Atime&#xff08;访问时间&#xff09;和Ctime&#xff08;修改时间&a…

带头双向循环链表(增、删 、查、改)基本操作详细介绍 必看!!!

文章目录 链表介绍链表初始化链表打印查找元素增加节点头插尾插在指定位置插入 删除节点头删尾删删除指定位置节点 链表判空获取链表中元素的个数链表销毁 链表介绍 前面说到&#xff0c;链表的结构一共有八种&#xff1a;带头单向循环链表、带头单向非循环链表、带头双向循环…

渗透测试--3.2捕获和监听网络数据

目录 1.监听捕获数据方法 2.kali监听捕获工具介绍 arpspoof driftnet 1.监听捕获数据方法 渗透测试中&#xff0c;捕获和监听网络数据是非常重要的一项任务&#xff0c;可以帮助我们发现潜在的漏洞和攻击面。以下是一些常见的捕获和监听网络数据的方法&#xff1a; 抓包工…

Liunx基础命令 - cp复制命令

cp命令 – 复制文件或目录 cp命令来自英文单词“copy”的缩写&#xff0c;中文译为“复制”&#xff0c;其功能是用于复制文件或目录。cp命令能够将一个或多个文件或目录复制到指定位置&#xff0c;亦常用于文件的备份工作。-r参数用于递归操作&#xff0c;复制目录时若忘记添…

网络编程——UDP编程

UDP编程 UDP编程步骤通信流程serverclient 函数接口socketbindrecvfromsendto 举例UDP客户端UDP服务器 UDP编程步骤 在C语言中进行UDP编程的一般步骤如下&#xff1a; &#xff08;1&#xff09;包含头文件&#xff1a; 在代码中包含必要的头文件&#xff0c;以便使用UDP编程所…