C++(12): std::mutex及其高级变种的使用

1. 简述

在多线程或其他许多场景下,同时对一个变量或一段资源进行读写操作是一个比较常见的过程,保证数据的一致性和防止竞态条件至关重要。

C++的标准库中为我们提供了使用的互斥及锁对象,帮助我们实现资源的互斥操作。

2. std::mutex及其衍生互斥手段

(1)互斥类

std::mutex,最基本的 mutex 类。

std::recursive_mutex,递归 mutex 类。

std::time_mutex,定时 mutex 类。

std::recursive_timed_mutex,定时递归 mutex 类。

(2)RAII上锁

std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。

std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

(3)API

std::try_lock,尝试上锁。如果当前互斥量已经被其他线程占用,当前线程不会阻塞,而是立即返回false。如果当前互斥量没有被其他线程占用,当前线程会获得该互斥量,完成上锁。需要注意的是,如果当前线程已经获得了该互斥量,那么再次进行try_lock就会造成死锁。

std::lock,上锁。调用该API会将互斥两上锁,如果当前互斥量已经被其他线程占用,则会阻塞,知道当前线程获得该锁。

std::unlock:解锁。

std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

3. std::mutex使用

        std::mutex是最简单的互斥量,可以单独使用为资源创建互斥环境,也可以与std::lock_guard合起来使用,实现一个RAII的应用。

        需要注意的是,std::mutex仅支持一次加锁和解锁。如下是一个简单地小程序。

/** 引用头文件. */#include <mutex>/** 创建互斥量. */std::mutex mtx;/** 需要共享的某个资源 */int sharedResource;/** 操作上述共享资源的函数 */void accessResource() {/** 尝试加锁 */mtx.lock();/** 临界区开始 - 访问共享资源 */sharedResource += 1;/** 临界区结束 - 释放锁 */mtx.unlock();}

        接下来是一个配合lock_guard使用的例程。

/** 引用头文件. */#include <mutex>/** 创建互斥量. */std::mutex mtx;/** 需要共享的某个资源 */int sharedResource;/** 操作上述共享资源的函数 */void accessResource() {/** 尝试加锁 */std::lock_guard<std::mutex> guard(mtx); // 自动加锁/** 临界区开始 - 访问共享资源 */sharedResource += 1;/** 退出函数,自动释放. */}

4. std::recursive_mutex递归锁

        从名字可以看出,递归所是可以多次上锁的,当然也需要配合多次解锁,通常情况下也仅用在递归环境下。

        如下是简单的使用std::recursive_mutex的示例。

#include <iostream>#include <thread>#include <mutex>std::recursive_mutex mtx;void func(int n) {mtx.lock();std::cout << "Thread " << n << " locked the mutex" << std::endl;if (n > 1) {func(n - 1);}std::cout << "Thread " << n << " unlocked the mutex" << std::endl;mtx.unlock();}int main() {std::thread t1(func, 3);std::thread t2(func, 2);t1.join();t2.join();return 0;}

        如下是配合lock_guard使用的示例。

#include <iostream>#include <thread>#include <mutex>std::recursive_mutex mtx;void func(int n) {std::lock_guard<std::recursive_mutex> guard(mtx);std::cout << "Thread " << n << " locked the mutex" << std::endl;if (n > 1) {func(n - 1);}std::cout << "Thread " << n << " unlocked the mutex" << std::endl;}int main() {std::thread t1(func, 3);std::thread t2(func, 2);t1.join();t2.join();return 0;}

5. std::timed_mutex

        std::timed_mutex 类似于 std::mutex,也是一个较为简单的锁。但是它额外提供了两个接口分别是 try_lock_for() 和 try_lock_until() 成员函数。前者允许线程尝试在一段时间内获取锁,如果在指定的时间内未能获得锁,线程将返回失败,并且可以根据返回值来判断是否继续等待或者执行其他逻辑。后者是一个确定的时间点,当到达指定的时间点以后,互斥锁不能够使用,则返回。

        使用 std::timed_mutex 可以帮助避免线程因为获取锁时长时间阻塞而导致程序性能下降或死锁情况的发生。

6. std::lock_guard和std::unique_lock

        std::lock_guard 和 std::unique_lock 都是 C++ 标准库中用于管理互斥量(mutex)的 RAII(Resource Acquisition Is Initialization,资源获取即初始化)包装器。它们都可以确保在持有互斥量的作用域内,互斥量会被安全地锁定和解锁,从而避免死锁和其他并发问题。不过,std::unique_lock 比 std::lock_guard 提供了更多的灵活性和功能。下面是它们的一些主要区别以及使用示例。

        我们在前面第3节和第4节都列举了使用lock_guard的使用,lock_guard的优点是使用简单,缺点是过于简单了。

        unique_lock能够实现和lock_guard一样的动能,也提供了更灵活的上锁和解锁控制。

        unique_lock含有第二参数,如下所示:


std::adopt_lock :表示这个互斥量已经被lock了,你必须要把互斥量提前lock了,否则会报异常。std::adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(也就是已经lock成功了),通知lock_guard和unique_lock不需要 再构造函数中lock这个互斥量了。

std::try_to_lock:我们会尝试用mutex的lock()去锁定这个mutex,但是如果没有锁定成功,也会立即返回,并不会阻塞在那里;使用这个try_to_lock的前提是你自己不能先lock。

std::defer_lock:不给mutex加锁,初始化了一个没有加锁的mutex。

前面讲到,unique_lock比lock_guard更为灵活,体现在哪里呢?事实上,unique_lock还拥有自己的成员函数,我们可以灵活的调用它的成员函数进行加解锁,而不是依赖于RAII。


        unique_lock的成员函数如下

lock:调用所管理的mutex对象的lock函数;

try_lock:调用所管理的mutex对象的try_lock函数;

try_lock_for:调用所管理的mutex对象的try_lock_for函数

try_lock_until:调用所管理的mutex对象的try_lock_until函数;

unlock:调用所管理的mutex对象的unlock函数;

release :返回所管理的mutex对象的指针,并释放所有权,但不改变mutex对象的状态;

owns_lock:返回当前std::unique_lock对象是否获得了锁;

mutex:返回当前std::unique_lock对象所管理的mutex对象的指针;

swap:交换两个unique_lock对象;

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

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

相关文章

QT - 日志:qDebug/qInfo/qWarning/qCritical

篇一、日志打印函数 头文件&#xff1a; #include <QDebug> 代码&#xff1a;qDebug()<<"hello world!"; 其他打印级别&#xff1a; qInfo(): 普通信息 qDebug(): 调试信息 qWarning(): 警告信息 qCritical(): 严重错误 qFatal(): 致命错误 1. qDebug…

【Leetcode】279.完全平方数

一、题目 1、题目描述 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。 示例1: 输入:n = 12 输出:3 解释:1…

Vue ElementPlus Form、Form-item 表单

Form 表单 由输入框、选择器、单选框、多选框等控件组成&#xff0c;用以收集、校验、提交数据&#xff0c;组件升级采用了 flex 布局&#xff0c;以替代旧版本的 float 布局。 在 Element Plus 中&#xff0c;el-form 是一个表单组件&#xff0c;用于创建表单以便用户填写和提…

东方 - 循环(2) - 求和计数

目录 解析部分&#xff1a;求和计数1002. 编程求解123...n问题描述解题思路代码实现代码解析 1741. 求出1~n中满足条件的数的个数和总和问题描述解题思路代码实现代码解析 1003. 编程求 \(1 3 5 ... n\)问题描述解题思路代码实现代码解析 1004. 编程求1*2*3*...*n问题描述解…

C语言:文件操作(一)

目录 前言 1、为什么使用文件 2、什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 3、文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 结&#xff08;一&#xff09; 前言 本篇文章将介绍C语言的文件操作&#xff0c;在后面的内容讲到&#xff1a;为什么使用文…

《Effective C++》《构造/析构/赋值运算——9、绝不在构造和析构过程中调用virtual函数》

文章目录 1、Terms 9:Never call virtual functions during construction or destruction1.1为什么不要在构造、析构函数中调用 virtual 函数1.1.1经典错误1.1.2 隐藏错误 1.2优化做法&#xff1a; 2、面试相关3、总结4、参考 1、Terms 9:Never call virtual functions during …

网络编程的学习2

UDP通信协议 发送数据 package UDPDEmo;import java.io.IOException; import java.net.*; import java.nio.charset.StandardCharsets;public class SendMessageDemo {public static void main(String[] args) throws IOException {//发送数据//1.创建对象//细节&#xff1a;…

PCL点云库出现错误:..\dist.h(523): error C3861: “pop_t”: 找不到标识符

工程代码&#xff1a;简单地测试了k-d树的最近邻搜索功能 #include<pcl/point_cloud.h> #include<pcl/kdtree/kdtree_flann.h>#include<iostream> #include<vector> #include<ctime>using namespace std;int main(int argc, char** argv) {//使…

回溯算法|78.子集

力扣题目链接 class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {result.push_back(path); // 收集子集&#xff0c;要放在终止添加的上面&#xff0c;否则会漏掉自…

Pygame基础8-碰撞

Collisions 在Pygame中&#xff0c;我们使用矩形来移动物体&#xff0c;并且用矩形检测碰撞。 colliderect检测两个矩形是否碰撞&#xff0c;但是没法确定碰撞的方向。 Rect1.colliderect(Rect2) # collision -> return Ture # else -> return Falsecollidepoint可以…

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭&#xff0c;从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口&#xff0c;这个接口只有两个方法start和stop两个方法&#xff0c;但是这个接口并不是直接提供给开发者做拓展点&#xff0c;而…

如何理解 Java 中的成员变量、字段和属性

在Java编程中&#xff0c;成员变量、字段和属性是关键概念&#xff0c;用于存储对象状态信息的变量。虽然它们经常被用作同义词&#xff0c;但在实际应用中&#xff0c;它们有着微妙的区别。 1. 成员变量&#xff08;Member Variable&#xff09; 成员变量是类中声明的任何变…

AI音乐GPT时刻来临:Suno 快速入门手册!

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

代码块的理解

如果成员变量想要初始化的值不是一个硬编码的常量值&#xff0c;而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值&#xff0c;该怎么办呢&#xff1f;此时&#xff0c;可以考虑代码块&#xff08;或初始化块&#xff09;。 代码块(或初始化块)的作…

前端作业之完成学校官方网页的制作

&#xff08;未使用框架&#xff0c;纯html和css制作&#xff09; 注&#xff1a;由本人技术限制&#xff0c;代码复用性极差 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>xxx大学</tit…

SpringBoot接收参数的方式

Get 请求 1.1 以方法的形参接收参数 1.这种方式一般适用参数比较少的情况 RestController RequestMapping("/user") Slf4j public class UserController {GetMapping("/detail")public Result<User> getUserDetail(String name,String phone) {log.…

zip解压异常java.lang.IllegalArgumentException: MALFORMED处理

使用hutool解压zip包时出错&#xff1a; //压缩包解压到固定目录 ZipUtil.unzip(tempZipFile,dir);在解压文件的时候报错&#xff0c;原因是压缩文件中有中文&#xff1b;导致错误&#xff0c;解决办法是设置编码&#xff1a; ZipFile tempZipFile new ZipFile(zipFile, Cha…

【Linux】详解动静态库的制作和使用动静态库在系统中的配置步骤

一、库的作用 1、提高开发效率&#xff0c;让开发者所有的函数实现不用从零开始。 2、隐藏源代码。 库其实就是所有的.o文件用特定的方式进行打包形成一个文件&#xff0c;各个.o文件包含了源代码中的机器语言指令。 二、动态库和静态库的制作和使用 2.1、静态库的制作和使用…

如何监控特权帐户,保护敏感数据

IT基础设施的增长导致员工可以访问的凭据和资源数量急剧增加。每个组织都存储关键信息&#xff0c;这些信息构成了做出关键业务决策的基石。与特权用户共享这些数据可以授予他们访问普通员工没有的凭据的权限。如果特权帐户凭证落入不法分子之手&#xff0c;它们可能被滥用&…

使用WebRTC实现简单直播

WebRTC 是一个强大的实时通信技术&#xff0c;它允许用户直接在网页浏览器之间进行音视频通话和数据共享&#xff0c;无需任何外部插件。结合 WebSocket&#xff0c;我们可以构建一个简单的直播系统&#xff0c;让用户能够发布自己的实时视频流&#xff0c;同时允许其他用户观看…