深入理解C++中的锁

目录

1.基本互斥锁(std::mutex)

2.递归互斥锁(std::recursive_mutex)

3.带超时机制的互斥锁(std::timed_mutex)

4.带超时机制的递归互斥锁(std::recursive_timed_mutex)

5.共享互斥锁也叫读写锁(std::shared_mutex)

6.带超时机制的共享互斥锁(std::shared_timed_mutex)

7.自旋锁

8.总结


1.基本互斥锁(std::mutex)

含义: std::mutex是最基本的互斥锁,主要用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

使用场景: 当需要保护共享资源不被多个线程同时修改时使用。

特点:简单易用,适用于大多数场景;不能递归锁定,同一线程多次尝试锁定会导致死锁。

以下是一个简单的示例,展示了如何使用 std::mutex 来保护共享数据:

#include <iostream>  
#include <thread>  
#include <mutex>  std::mutex mtx;     //全局互斥锁
int shared_data = 0;   //共享数据void increment_shared_data(int n) {  for (int i = 0; i < n; ++i) {  std::lock_guard<std::mutex> lock(mtx);  ++shared_data;  }  
}  int main() {  std::thread t1(increment_shared_data, 1000);  std::thread t2(increment_shared_data, 1000);  t1.join();  t2.join();  std::cout << "Shared data: " << shared_data << std::endl;  return 0;  
}

 这个程序创建了2个线程,每个线程尝试对counter增加10000次。通过使用std::mutex, 我们确保每次只有一个线程可以增加计数器,避免了数据竞争。

2.递归互斥锁(std::recursive_mutex)

含义std::recursive_mutex允许同一线程多次获取锁而不会发生死锁,这对于递归函数或需要多次锁定的场景非常有用。

使用场景: 在递归函数中需要多次获取同一个锁的情况。

特点:适用于递归调用和需要多次锁定的场景;需要注意避免滥用,因为递归锁的使用会增加锁定次数的复杂性。

示例如下:

#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex rmtx;void recursive_function(int depth) {rmtx.lock();std::cout << "Depth: " << depth << std::endl;if (depth > 0) {recursive_function(depth - 1);}rmtx.unlock();
}int main() {std::thread t(recursive_function, 5);t.join();return 0;
}

这段代码在递归函数recursive_function中使用std::recursive_mutex。每次调用都会尝试加锁,由于使用的是递归互斥锁,同一线程可以多次成功获取锁。

3.带超时机制的互斥锁(std::timed_mutex)

含义std::timed_mutexstd::mutex的基础上增加了超时功能,允许线程在指定时间内尝试获取锁,如果在超时时间内未成功获取锁,则返回失败。

使用场景: 当你不希望线程因等待锁而无限期阻塞时使用。

特点:适用于需要设置锁获取超时时间的场景;提供try_lock_fortry_lock_until两种超时尝试获取锁的方法。

示例如下:

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <chrono>  std::timed_mutex mtx;  void try_lock_function() {  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Lock acquired!\n";  // 执行受保护的操作  std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作  mtx.unlock(); // 显式解锁  } else {  std::cout << "Failed to acquire lock within timeout.\n";  }  
}  int main() {  std::thread t1(try_lock_function);  std::thread t2(try_lock_function);  t1.join();  t2.join();  return 0;  
}

在这个例子中,两个线程都尝试在 1 秒内获取锁。由于互斥锁在同一时刻只能被一个线程持有,因此至少有一个线程将无法在超时时间内获取锁,并输出相应的消息。

4.带超时机制的递归互斥锁(std::recursive_timed_mutex)

含义std::recursive_timed_mutex结合了std::recursive_mutexstd::timed_mutex的特性,支持递归锁定和超时机制。

使用场景: 适用于需要递归锁定资源,并且希望能够设置尝试获取锁的超时时间的场景。这在需要防止线程在等待锁时无限阻塞的复杂递归调用中特别有用。

特点:适用于递归调用和需要超时机制的场景;提供超时尝试获取递归锁的方法。

示例如下:

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <chrono>  std::recursive_timed_mutex mtx;  void recursive_lock_function() {  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Lock acquired!\n";  // 递归锁定  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Recursive lock acquired!\n";  mtx.unlock(); // 释放递归锁  } else {  std::cout << "Failed to acquire recursive lock within timeout.\n";  }  // ... 执行受保护的操作  mtx.unlock(); // 释放原始锁  } else {  std::cout << "Failed to acquire lock within timeout.\n";  }  
}  int main() {  std::thread t1(recursive_lock_function);  std::thread t2(recursive_lock_function);  t1.join();  t2.join();  return 0;  
}

请注意,由于 std::recursive_timed_mutex 允许递归锁定,上面的示例中展示了如何在已经持有锁的情况下再次尝试获取锁(尽管在这个特定示例中,第二次尝试获取锁是多余的,因为我们已经持有锁了)。然而,在实际情况中,递归锁定可能用于更复杂的场景,其中函数可能会递归调用自己,并且每个递归调用都需要访问受保护的数据。

5.共享互斥锁也叫读写锁(std::shared_mutex)

含义std::shared_mutex允许多个线程同时读取,但只有一个线程可以写入。这在读多写少的场景下非常有用。

使用场景: 适用于读操作远多于写操作的情况。

特点:适用于读多写少的场景;读操作和写操作使用不同的锁定机制。

示例如下:


#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex shmtx;void read_shared(int id) {std::shared_lock<std::shared_mutex> lock(shmtx); // 共享锁std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}void write_shared(int id) {std::unique_lock<std::shared_mutex> lock(shmtx); // 独占锁std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}int main() {std::thread readers[5], writer(write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}

输出结果可能会有所不同,因为读写顺序由操作系统的线程调度决定。本例中,一个写线程在修改数据,多个读线程在同时读数据。通过std::shared_mutex,我们允许多个读操作同时进行,但写操作是独占的。

6.带超时机制的共享互斥锁(std::shared_timed_mutex)

含义std::shared_timed_mutex 是 C++ 标准库中的一个同步原语,它结合了 std::shared_mutex(共享互斥锁)和超时机制的特性。std::shared_mutex 允许多个线程同时以共享模式持有锁(即读取操作可以并发执行),但每次只有一个线程能以独占模式持有锁(即写入操作是互斥的)。通过添加超时机制,std::shared_timed_mutex 允许线程尝试以共享模式或独占模式获取锁,并设置一个超时时间,如果在这段时间内未能成功获取锁,则可以放弃并继续执行其他操作。

使用场景:当你不希望线程因等待锁而无限期阻塞时使用。

特点:适用于读多写少且需要超时机制的场景;提供超时尝试获取共享锁的方法。

示例如下:

#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>std::shared_timed_mutex shtmmtx;void try_read_shared(int id) {if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock_shared();} else {std::cout << "Thread " << id << " could not read" << std::endl;}
}void try_write_shared(int id) {if (shtmmtx.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock();} else {std::cout << "Thread " << id << " could not write" << std::endl;}
}int main() {std::thread readers[5], writer(try_write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(try_read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}

7.自旋锁

含义:在C++中,自旋锁(spinlock)是一种低级的同步机制,用于保护共享资源,防止多个线程同时访问。与互斥锁(mutex)不同,当自旋锁被锁定时,尝试获取锁的线程会不断循环检查锁是否可用,而不是进入睡眠状态等待锁被释放。这意味着,自旋锁在等待时间很短的情况下是非常有效的,但如果等待时间过长,会导致CPU资源的浪费。

C++标准库本身并不直接提供自旋锁的实现,但你可以使用<atomic>库中的原子操作来手动实现一个自旋锁,或者使用特定平台提供的API(如Windows的SRWLOCK或POSIX的pthread_spinlock_t)。

使用场景:自旋锁适用于锁持有时间非常短且线程不希望在操作系统调度中频繁上下文切换的场景。这通常用在低延迟系统中,或者当线程数量不多于CPU核心数量时,确保CPU不会在等待锁时空闲。

示例如下:

#include <atomic>  
#include <iostream>  
#include <thread>  
#include <chrono>  class Spinlock {  
private:  std::atomic_flag lock_ = ATOMIC_FLAG_INIT;  public:  void lock() {  while (lock_.test_and_set(std::memory_order_acquire)) {  // 循环直到锁被释放  }  }  void unlock() {  lock_.clear(std::memory_order_release);  }  bool try_lock() {  return !lock_.test_and_set(std::memory_order_acquire);  }  
};  void threadFunction(Spinlock& lock, int id) {  lock.lock();  std::cout << "Thread " << id << " entered critical section\n";  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时操作  std::cout << "Thread " << id << " leaving critical section\n";  lock.unlock();  
}  int main() {  Spinlock lock;  std::thread t1(threadFunction, std::ref(lock), 1);  std::thread t2(threadFunction, std::ref(lock), 2);  t1.join();  t2.join();  return 0;  
}

        在这个例子中,Spinlock 类使用了一个 std::atomic_flag 类型的成员变量 lock_ 来实现锁的功能。lock_ 的 test_and_set 方法会尝试将标志设置为 true 并返回之前的值。如果返回 false,表示锁之前未被锁定,当前线程成功获取锁;如果返回 true,表示锁已被其他线程持有,当前线程需要继续循环等待。

        请注意,自旋锁在多核处理器上且等待时间较短时通常表现良好,但在等待时间较长或锁竞争激烈时可能会导致性能问题。因此,在选择使用自旋锁时,需要根据具体的应用场景和性能要求做出合理的选择。

8.总结

        C++标准库提供了多种类型的互斥锁,每种锁都有其特定的用途和特点。选择合适的互斥锁类型可以有效提高程序的并发性能和安全性。

C++惯用法之RAII思想: 资源管理_raii 思想-CSDN博客

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

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

相关文章

[论文阅读笔记33] Matching Anything by Segmenting Anything (CVPR2024 highlight)

这篇文章借助SAM模型强大的泛化性&#xff0c;在任意域上进行任意的多目标跟踪&#xff0c;而无需任何额外的标注。 其核心思想就是在训练的过程中&#xff0c;利用strong augmentation对一张图片进行变换&#xff0c;然后用SAM分割出其中的对象&#xff0c;因此可以找到一组图…

网络爬虫基础知识

文章目录 网络爬虫基础知识爬虫的定义爬虫的工作流程常用技术和工具爬虫的应用1. 抓取天气信息2. 抓取新闻标题3. 抓取股票价格4. 抓取商品价格5. 抓取博客文章标题 网络爬虫基础知识 爬虫的定义 网络爬虫&#xff08;Web Crawler 或 Spider&#xff09;是一种自动化程序&…

《企业实战分享 · 常用运维中间件》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;如需交流&#xff…

HUAWEI MPLS 静态配置和动态LDP配置

MPLS(Multi-Protocol Label Switching&#xff0c;多协议标签交换技术)技术的出现&#xff0c;极大地推动了互联网的发展和应用。例如&#xff1a;利用MPLS技术&#xff0c;可以有效而灵活地部署VPN(Virtual Private Network&#xff0c;虚拟专用网)&#xff0c;TE(Traffic Eng…

a-range-picker 国际化不生效

1、问题&#xff1a;按照官方 添加后是这样的 周和月没有翻译 1-1官方配置如下图 1-2效果&#xff1a; 2、import locale from "ant-design-vue/es/date-picker/locale/zh_CN"; 打印出locale是这样的 这个文件翻译文件中没有相关翻译 3、解决&#xff1a; 简单粗…

【实战场景】记一次UAT jvm故障排查经历

【实战场景】记一次UAT jvm故障排查经历 开篇词&#xff1a;干货篇&#xff1a;1.查看系统资源使用情况2.将十进制进程号转成十六进制3.使用jstack工具监视进程的垃圾回收情况4.输出指定线程的堆内存信息5.观察日志6.本地环境复现 总结篇&#xff1a;我是杰叔叔&#xff0c;一名…

Objective-C使用块枚举的细节

对元素类型的要求 在 Objective-C 中&#xff0c;NSArray 只能存储对象类型&#xff0c;而不能直接存储基本类型&#xff08;例如 int&#xff09;。但是&#xff0c;可以将基本类型封装在 NSNumber 等对象中&#xff0c;然后将这些对象存储在 NSArray 中。这样&#xff0c;en…

H6922 便携移动储能升压恒压方案 2.8-40V耐压 7.5A大电流应用芯片IC

H6922芯片是一款便携移动储能升压恒压控制驱动芯片&#xff0c;满足2.8-40V宽输入电压范围的升压恒压电源应用而设计。下面我将基于您提供的信息&#xff0c;对H6922的特性和典型应用进行更详细的解释。 产品特性详解 宽输入电压&#xff1a;H6922支持2.8-40V的宽输入电压范围…

【Windows】Visual Studio Installer下载缓慢解决办法

【Windows】Visual Studio Installer下载缓慢解决办法 1.背景2.分析3.结果 1.背景 使用visual studio在线安装包进行IDE安装&#xff0c;发现下载几乎停滞&#xff0c;网速几乎为零。 经过排查并不是因为实际网络带宽导致。 这里涉及DNS知识&#xff1b; DNS&#xff08;Dom…

SCI丨5分期刊,JCR一区

SCI&#xff0c;5分&#xff0c;JCR Q1&#xff0c;中科大类3小类2区 1 基于复杂网络与xxx能源汽车节能数值分析 2 基于热能损失优化的xxx与性能管理 3 基于xxxLCA技术的绿色制造工艺优化研究 4 基于xxx入侵检测技术的物联网智能制造监控系统设计 6 基于物联网技术xxx电力系…

BMP280 环境传感器

型号简介 BMP280是博世&#xff08;bosch-sensortec&#xff09;的一款气压传感器&#xff0c;特别适用于移动应用。其小尺寸和低功耗使其能够应用于电池供电的设备&#xff0c;如手机、GPS 模块或手表。基于博世久经考验的压阻式压力传感器技术&#xff0c;具有高精度和线性度…

Elasticsearch备份数据到本地,并导入到新的服务 es 服务中

文章目录 使用elasticsearch-dump工具备份安装node.js(二进制安装)解压设置环境变量安装elasticsearch-dump docker安装使用ES备份文件到本地 使用elasticsearch-dump工具备份 这个工具备份时间比较长 安装node.js(二进制安装) wget https://nodejs.org/dist/v16.18.0/node-…

SpringBoot: Eureka入门

1. IP列表 公司发展到一定的规模之后&#xff0c;应用拆分是无可避免的。假设我们有2个服务(服务A、服务B)&#xff0c;如果服务A要调用服务B&#xff0c;我们能怎么做呢&#xff1f;最简单的方法是让服务A配置服务B的所有节点的IP&#xff0c;在服务A内部做负载均衡调用服务B…

基于FPGA的DDS信号发生器

前言 此处仅为基于Vivado实现DDS信号发生器的仿真实现&#xff0c;Vivado的安装请看下面的文章&#xff0c;这里我只是安装了一个标准版本&#xff0c;只要能够仿真波形即可。 FPGA开发Vivado安装教程_vivado安装 csdn-CSDN博客 DDS原理 DDS技术是一种通过数字计算生成波形…

图解 Kafka 架构

写在前面 Kafka 是一个可横向扩展&#xff0c;高可靠的实时消息中间件&#xff0c;常用于服务解耦、流量削峰。 好像是 LinkedIn 团队开发的&#xff0c;后面捐赠给apache基金会了。 kafka 总体架构图 Producer&#xff1a;生产者&#xff0c;消息的产生者&#xff0c;是消息的…

排序算法系列一:选择排序、插入排序 与 希尔排序

目录 零、说在前面 一、理论部分 1.1&#xff1a;选择排序 1.1.1&#xff1a;算法解读&#xff1a; 1.1.2&#xff1a;时间复杂度 1.1.3&#xff1a;优缺点&#xff1a; 1.1.4&#xff1a;代码&#xff1a; 1.2&#xff1a;插入排序 1.2.1&#xff1a;算法解读&#x…

上位机图像处理和嵌入式模块部署(mcu 项目1:上位机编写)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面&#xff0c;我们说过要做一个报警器。如果只是简单做一个报警器呢&#xff0c;这个基本上没有什么难度。这里&#xff0c;我们就适当提高一下…

某业帮六月校招后端笔试

题目一 解题思路 签到题&#xff0c;dp就行。 题目二 解题思路 这个比较烦人&#xff0c;需要处理额外的引号和括号。用DFS&#xff0c;对于每个间隙&#xff0c;插入与不插入都搜一遍。 题目三 解题思路&#xff1a; 双指针&#xff0c;左右各一个指针&#xff0c;对比长度&…

如何在Python中拷贝类对象到数组

1、问题背景 在Python中&#xff0c;我们经常需要存储多个对象的集合。有时&#xff0c;我们需要拷贝这些对象&#xff0c;以便在不修改原始对象的情况下对它们进行操作。例如&#xff0c;在下述代码中&#xff0c;我们在colors列表中存储了多个Color对象&#xff0c;然后我们创…

单线激光雷达-多线激光雷达安装测试

单线激光雷达思岚 S 系列 参数简介 单线激光雷达的参数&#xff0c;主要看扫描频率&#xff08;Hz&#xff09;&#xff0c;扫描范围&#xff08;度&#xff09;&#xff0c;最大测距距离&#xff08;m&#xff09;以及单 圈点数&#xff09;。机器人运动速度越快&#xff0c…