深入理解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.mzph.cn/web/38587.shtml

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

相关文章

【python脚本】批量检测sql延时注入

文章目录 前言批量检测sql延时注入工作原理脚本演示 前言 SQL延时注入是一种在Web应用程序中利用SQL注入漏洞的技术&#xff0c;当传统的基于错误信息或数据回显的注入方法不可行时&#xff0c;例如当Web应用进行了安全配置&#xff0c;不显示任何错误信息或敏感数据时&#x…

【TS】TypeScript 原始数据类型深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript 原始数据类型深度解析一、引言二、基础原始数据类型2.1 boolean2.2 …

苍穹外卖--sky-take-out(四)10-12

苍穹外卖--sky-take-out&#xff08;一&#xff09; 苍穹外卖--sky-take-out&#xff08;一&#xff09;-CSDN博客​编辑https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2…

Unity动画系统(2)

6.1 动画系统基础2-3_哔哩哔哩_bilibili p316 模型添加Animator组件 动画控制器 AnimatorController AnimatorController 可以通过代码控制动画速度 建立动画间的联系 bool值的设定 trigger p318 trigger点击的时候触发&#xff0c;如喊叫&#xff0c;开枪及换子弹等&#x…

错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 python ping

报错提示&#xff1a;错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 用python做了一个批量ping脚本&#xff0c;在windows专业版上没问题&#xff0c;但是到了windows服务器就出现这个报错 解决方法&#xff1a;右键 管理员身份运行 这个脚本 …

sql拉链表

1、定义&#xff1a;维护历史状态以及最新数据的一种表 2、使用场景 1、有一些表的数据量很大&#xff0c;比如一张用户表&#xff0c;大约1亿条记录&#xff0c;50个字段&#xff0c;这种表 2.表中的部分字段会被update更新操作&#xff0c;如用户联系方式&#xff0c;产品的…

在 WebGPU 与 Vulkan 之间做出正确的选择(Making the Right Choice between WebGPU vs Vulkan)

在 WebGPU 与 Vulkan 之间做出正确的选择&#xff08;Making the Right Choice between WebGPU vs Vulkan&#xff09; WebGPU 和 Vulkan 之间的主要区别WebGPU 是什么&#xff1f;它适合谁使用&#xff1f;Vulkan 是什么&#xff1f;它适合谁使用&#xff1f;WebGPU 和 Vulkan…

修改CentOS7 yum源

修改CentOS默认yum源为阿里镜像源 备份系统自带yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 下载ailiyun的yum源配置文件 CentOS7 yum源如下&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun…

[OC]萝卜圈Python手动机器人脚本

这是给机器人设置的端口&#xff0c;对照用 代码 # #作者:溥哥’ ##机器人驱动主程序 #请在main中编写您自己的机器人驱动代码 import msvcrt def main():a"none"while True:key_input msvcrt.getch()akey_inputif abw:print(a)robot_drv.set_motors(1,40,2,40,3,…

uniapp学习笔记

uniapp官网地址&#xff1a;https://uniapp.dcloud.net.cn/ 学习源码&#xff1a;https://gitee.com/qingnian8/uniapp-ling_project.git 颜色网址&#xff1a;https://colordrop.io/ uniapp中如何获取导航中的路由信息&#xff1f; onLoad(e){console.log(e)console.log(e.w…

2.2.4 C#中显示控件BDPictureBox 的实现----ROI交互

2.2.4 C#中显示控件BDPictureBox 的实现----ROI交互 1 界面效果 在设定模式下&#xff0c;可以进行ROI 框的拖动&#xff0c;这里以Rect1举例说明 2 增加ROI类定义 /// <summary> /// ROI_single /// 用于描述图片感兴趣区域 /// type: 0:Rect1;1:Rect2;2:Circle ;3:…

工业路由器与家用路由器的区别

在现代网络环境中&#xff0c;路由器扮演着至关重要的角色。无论是在家庭网络还是在工业网络&#xff0c;选择合适的路由器都至关重要。本文将从多个角度&#xff0c;对工业路由器与家用路由器进行详细比较&#xff0c;帮助您更好地理解二者的区别。 1、安全性 工业路由器&…

大模型应用开发实战基础

大模型应用开发实战基础 1. 背景 大模型如日中天&#xff0c;各行各业都受它影响&#xff0c;但是作为程序员&#xff0c;除了让它翻译代码不知道用它干什么&#xff0c;就像是拿着锤子的木匠&#xff0c;找不到钉子在哪。一边听着别人说2024是AI元年&#xff0c;一边又不知所…

中控室监控台在水处理行业的作用

随着工业化和城市化的快速推进&#xff0c;水处理行业的重要性日益凸显。作为确保水质安全、提高水资源利用效率的关键环节&#xff0c;水处理厂需要高效、稳定地运行。在这个过程中&#xff0c;中控室监控台发挥着不可或缺的作用。本文将从以下几个方面&#xff0c;详细阐述中…

fyne的MultiLineEntry设置大小

MultiLineEntry设置大小 在另一篇文章讲过&#xff0c;放入border布局中&#xff0c;可以最大化MultiLineEntry。 这里再介绍另一种方法:SetMinRowsVisible() func (e *Entry) SetMinRowsVisible(count int) {e.multiLineRows counte.Refresh() }SetMinRowsVisible强制mult…

九浅一深Jemalloc5.3.0 -- ④浅*配置

目前市面上有不少分析Jemalloc老版本的博文&#xff0c;但最新版本5.3.0却少之又少。而且5.3.0的架构与5之前的版本有较大不同&#xff0c;本着“与时俱进”、“由浅入深”的宗旨&#xff0c;我将逐步分析最新release版本Jemalloc5.3.0的实现。 另外&#xff0c;单讲实现代码是…

ShareSDK iOS端如何实现小红书分享

下载SDK 请登陆官网 &#xff0c;找到SDK下载&#xff0c;勾选需要的平台下载 导入SDK &#xff08;1&#xff09;离线导入将上述下载到的SDK&#xff0c;直接将整个SDK资源文件拖进项目里&#xff0c;如下图&#xff1a; 并且勾选以下3个选项 在点击Finish&#xff0c;…

JavaSE简易版扫雷小游戏

描述&#xff1a;用户输入二维雷区的高和宽&#xff0c;输入确定地雷数&#xff0c;随机在地雷区生成地雷。用户输入横竖坐标进行挖雷&#xff0c;挖到地雷游戏以失败结束&#xff0c;并让用户选择是否再次游戏&#xff1b;没挖到雷&#xff0c;显示该区域8个方向地雷数。如果8…

uniapp + vue3 + Script Setup 写法变动 (持续更新)

一、uniapp 应用生命周期&#xff1a; https://uniapp.dcloud.net.cn/tutorial/vue3-composition-api.html 注意&#xff1a; 应用生命周期仅可在App.vue中监听&#xff0c;在其它页面监听无效。 二 、uniapp页面生命周期&#xff1a; https://uniapp.dcloud.net.cn/tutori…

Golang | Leetcode Golang题解之第212题单词搜索II

题目&#xff1a; 题解&#xff1a; type Trie struct {children map[byte]*Trieword string }func (t *Trie) Insert(word string) {node : tfor i : range word {ch : word[i]if node.children[ch] nil {node.children[ch] &Trie{children: map[byte]*Trie{}}}nod…