【C++多线程编程:六种锁】

目录

普通互斥锁: 

轻量级锁

独占锁:

std::lock_guard:

std::unique_lock: 

共享锁:

超时的互斥锁

递归锁


普通互斥锁: 

std::mutex确保任意时刻只有一个线程可以访问共享资源,在多线程中常用于保护共享资源。

互斥锁的实现示例:

#include <iostream>
#include <thread>
#include <mutex>std::mutex Mutex;
int shared_source = 1;void Shared_data()
{//获取锁Mutex.lock();shared_source++;std::cout << "shared_source : " << shared_source <<" by thread :"<<std::this_thread::get_id()<< std::endl;//锁释放Mutex.unlock();
}
int main()
{std::thread thread1(Shared_data);std::thread thread2(Shared_data);thread1.join();thread2.join();return 0;
}

在实际使用的过程中,如果容易忘记对锁进行释放,可以使用std::unique_lock和std::lock_guard安全的管理锁的释放。 

轻量级锁

在C++中,轻量级锁通常指的是一些开销较小,性能较高的锁机制,C++并没有直接提供“轻量级锁”的概念,可以通过一个自旋锁来达到实现轻量级锁的目的。

适用场景:竞争不激烈,比如在大多数时间里中有一个线程需要访问临界区的情况。但如果竞争激烈会导致CUP资源的浪费(CPU自旋),降低性能。

自旋锁:一种简单的锁机制,当线程尝试获取锁时,如果锁已被其他线程占用,则当前线程会不断地循环等待,直到锁可用

自旋锁的示例:

#include <iostream>
#include <thread>
#include <atomic>std::atomic<bool>spinlock(false);void spin_lock()
{while (spinlock.exchange(true, std::memory_order_acquire)){//如果锁被占用则一直自旋,等待锁可用}
}void spin_unlock()
{spinlock.store(false, std::memory_order_release);//释放锁,并将锁的状态写入内存中
}
void critical_section()
{spin_lock();std::cout << "mutex acquire by thread : " << std::this_thread::get_id() << std::endl;
//	std::this_thread::sleep_for(std::chrono::seconds(1));spin_unlock();//锁释放
}
int main()
{std::thread t1(critical_section);std::thread t2(critical_section);t1.join();t2.join();return 0;
}

独占锁:

std::unique_lock和std::lock_guard都是cpp标准库提供的管理互斥锁的类,他们都提供了自动锁和解锁的功能。但二者存在一些关键区别:

std::lock_guard:

  • std::lock_guard并不是一种锁,而是一个作用域锁管理,一个能管理锁生命周期的工具,用于简化互斥锁的使用,确保在作用域结束后自动释放锁,避免死锁问题
  • 自动加锁和解锁:在构造时自动加锁,在析构时自动解锁,不需要显示的调用加锁和解锁的方法
  • 不支持条件变量配合使用:条件变量需要可以临时释放锁并重新获取锁,但是lock_guard并没有提供unlock().
  • 使用场景:适用于不需要显示控制加锁和解锁的场景,或者不需要与条件变量配合使用的场景,适用于在某个作用域中同步访问共享资源的场景

lock_guard示例:

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void worker()
{std::lock_guard<std::mutex>lock(mtx);//自动加锁shared_data++;std::cout << "shared_data ++ by thread : " << std::this_thread::get_id() << std::endl;//自动解锁,无需显示调用unlock()
}
int main()
{std::thread t1(worker);std::thread t2(worker);t1.join();t2.join();return 0;
}

std::unique_lock: 

  • std::unique_lock本身不是一种锁,而是一个可选的互斥锁管理器,提供了对互斥锁的更加灵活的控制方式
  • 显示加锁和解锁:提供了lock()和unlock()方法,可以显示的加锁和解锁,也可以在构造时自动加锁
  • 可以与条件变量配合
  • 自动管理锁的生命周期,避免了因忘记解锁而导致的死锁问题
  • 使用场景:需要显示控制加锁和解锁时机,或者需要和条件变量配合使用的场景

unique_lock实例:

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;
char str = 'a';//不显示调用加解锁,自动加锁和解锁
void worker()
{std::unique_lock<std::mutex>lock(mtx);//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁shared_data += 1;std::cout << "shared_data ++ :" << shared_data << " ,by thread : " << std::this_thread::get_id() << std::endl;std::cout << "str: " << str++ << " ,by thread: " << std::this_thread::get_id() << std::endl;}
//显示调用加锁和解锁
void worker()
{std::unique_lock<std::mutex>lock(mtx,std::defer_lock);//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁shared_data+=1;std::cout << "shared_data ++ :"<<shared_data<<" ,by thread : " << std::this_thread::get_id() << std::endl;lock.lock();//延迟锁定std::cout << "str: " << str++ <<" ,by thread: "<< std::this_thread::get_id() << std::endl;lock.unlock();//显示解锁
}int main()
{std::thread t1(worker);std::thread t2(worker);t1.join();t2.join();return 0;
}

独占锁与mutex相比,更加的安全,它可以避免忘记手动解锁,会在其作用域结束时自动的释放锁  

共享锁:

在C++中,共享锁允许多个线程共享读取资源,但在写入资源时只允许一个线程写入数据,要求独占锁。当某个线程获取了独占锁时,其他线程无法获取任何形式地锁,而当多个线程获取共享锁时,他们可以同时读取共享资源

使用场景:适用于读多写少的场景下,能有效提高多线程程序的性能

示例:

#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex mtx;
int shared_data = 0;
void read_data()
{//获取共享锁std::shared_lock <std::shared_mutex> lock(mtx);std::cout << "Reading data : " << shared_data << std::endl;
}
void write_data()
{//获取独占锁std::unique_lock<std::shared_mutex>lock(mtx);shared_data = 24;std::cout << "Writing data : " << shared_data << std::endl;
}
int main()
{std::thread t1(read_data);std::thread t2(read_data);std::thread t3(write_data);t1.join();t2.join();t3.join();return 0;
}

超时的互斥锁

C++标准库提供了timed_mutex来实现该功能,支持超时机制,当线程尝试获取锁时,可以指定一个超时时间,如果在该时间内无法获取锁,线程将返回并继续执行其他任务。通过使用超时互斥锁,可以有效的避免线程在等待锁时无限地阻塞,提高程序地响应和稳定性 

示例: 

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>//超时锁
//C++标准库提供的互斥锁之一,支持在尝试获取锁时设置超时时间
std::timed_mutex mtx;void function_time()
{//在1s内获取锁//try_lock_for在指定时间内获取锁,成功返回true,失败则false//参数chrono是一个时间间隔类型的一个域if (mtx.try_lock_for(std::chrono::seconds(1))){std::cout << "Lock acquired " << std::endl;//模拟耗时操作//在当前线程休眠这2s内,其他线程无法获取该锁//所以当t2尝试获取锁时,t1持有锁并休眠2s,当锁释放后,t2锁获取超时std::this_thread::sleep_for(std::chrono::seconds(2));mtx.unlock();//释放锁}else{std::cout << "Fail to acquire lock within 1s" << std::endl;}
}
int main()
{//主线程创建的这俩线程几乎同时开始执行function_time函数std::thread t1(function_time);std::thread t2(function_time);t1.join();t2.join();return 0;
}

递归锁

递归锁:同一个线程多次获取同一把锁,而不会导致死锁。cpp中没有直接提供递归锁的实现,但是可以通过reecursive_mutex来实现递归锁的功能。

reecursive_mutex:一个可重入的互斥锁,允许同一个线程多次调用lock或try_lock来获取锁,不会导致死锁。

使用场景:需要在同一个线程中多次获取锁的场景

递归锁示例:

#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex mtx;void function(int n)//递归
{if (n > 0){mtx.lock();std::cout << "lock acquired by thread : " << std::this_thread::get_id() << " n ; " << n << std::endl;function(n - 1);mtx.unlock();}}int main()
{std::thread t1(function,3);std::thread t2(function,2);t1.join();t2.join();return 0;
}

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

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

相关文章

计算机视觉算法实战——车道线检测

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​​ ​​​​​​​​​​​​ ​​​​​ 车道线检测是计算机视觉领域的一个重要研究方向&#xff0c;尤其在自动驾驶和高级驾驶辅助…

【微服务】面试 3、 服务监控 SkyWalking

微服务监控的原因 问题定位&#xff1a;在微服务架构中&#xff0c;客户端&#xff08;如 PC 端、APP 端、小程序等&#xff09;请求后台服务需经过网关再路由到各个微服务&#xff0c;服务间可能存在多链路调用。当某一微服务挂掉时&#xff0c;在复杂的调用链路中难以迅速确定…

EasyExcel的应用

一、简单使用 引入依赖&#xff1a; 这里我们可以使用最新的4.0.2版本&#xff0c;也可以选择之前的稳定版本&#xff0c;3.1.x以后的版本API大致相同&#xff0c;新的版本也会向前兼容&#xff08;3.1.x之前的版本&#xff0c;部分API可能在高版本被废弃&#xff09;&…

【MySQL数据库】基础总结

目录 前言 一、概述 二、 SQL 1. SQL通用语法 2. SQL分类 3. DDL 3.1 数据库操作 3.2 表操作 4. DML 5. DQL 5.1 基础查询 5.2 条件查询 5.3 聚合函数 5.4 分组查询 5.5 排序查询 5.6 分页查询 6. DCL 6.1 管理用户 6.2 权限控制 三、数据类型 1. 数值类…

aws(学习笔记第二十三课) step functions进行开发(lambda函数调用)

aws(学习笔记第二十三课) 开发step functions状态机的应用程序 学习内容&#xff1a; step functions状态机的概念开发简单的step functions状态机 1. step functions状态机概念 官方说明文档和实例程序 AWS的官方给出了学习的链接和实例程序。使用SAM创建step functions 借…

安卓开发动画

1.gif图片动画 边缘会有锯齿 2.json动画 用lottie json文件动画 实现 Android Studio使用lottie&#xff0c;加载json文件&#xff0c;实现动画效果_android 加载json动画-CSDN博客 遇到的坑 1.不播放&#xff0c;可能因为设置了图片&#xff08;跟动画一样的图片&#xf…

【Docker】入门教程

目录 一、Docker的安装 二、Docker的命令 Docker命令实验 1.下载镜像 2.启动容器 3.修改页面 4.保存镜像 5.分享社区 三、Docker存储 1.目录挂载 2.卷映射 四、Docker网络 1.容器间相互访问 2.Redis主从同步集群 3.启动MySQL 五、Docker Compose 1.命令式安装 …

算法练习7——拦截导弹的系统数量求解

题目描述 某国为了防御敌国的导弹袭击&#xff0c;发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷&#xff1a;虽然它的第一发炮弹能够到达任意的高度&#xff0c;但是以后每一发炮弹都不能高于前一发的高度。 假设某天雷达捕捉到敌国的导弹来袭。由于该系统还在试用…

如何使用高性能内存数据库Redis

一、详细介绍 1.1、Redis概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。Redis支持多种类型的数据结构&#xff0c;如字符串&#xff08;strings&#xff09;、哈希&am…

【Linux系列】`find / -name cacert.pem` 文件搜索

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

最大拿牌的得分

假设有个游戏&#xff0c;一列牌有不同分数&#xff0c;但是只能从两头拿 &#xff0c;拿到最后分数最高的人获胜&#xff0c;假设两个人都是聪明人&#xff0c;求最后的最高分是多少&#xff1f; 思路&#xff1a;递归算法&#xff0c;一个人拿左边牌&#xff0c;另一个人的得…

UE材质Fab Megascans

2025年Bridge里已经不能直接导入资产了&#xff0c;显示GET IT ON FAB 只能在Fab中导入资产&#xff0c; 纹理打包技术从RMA改成了ORM O&#xff1a;AO 环境光遮蔽 R&#xff1a;Roughness 粗糙度 M&#xff1a;Metallic 金属度 在Fab中找到材质&#xff0c;点击Add to P…

前后端本地启动

一、后端启动 1. 项目导入 目标&#xff1a;将后端代码从远程仓库&#xff08;GitHub&#xff09;导入到开发工具中&#xff08;例如 IntelliJ IDEA&#xff09;&#xff0c;方便我们对项目进行编辑和运行。 步骤&#xff1a; 打开 IntelliJ IDEA&#xff08;下文简称 IDEA…

ansible 检查目录大小

检查目录大小 worker_du.yml# ansible-playbook -i hosts worker_du.yml --limit w10 --- - name: 检查目录大小hosts:- w10 # 可以根据需要修改目标主机# 可以添加更多主机tasks:- name: 获取每台主机 /root/worker01 目录大小shell: du -sh /root/worker01/ | awk {print …

【NP-hard问题】NP与NP-hard问题通俗解释

最近在研究NP-hard问题&#xff0c;讲一下自己的对于NP与NP-hard问题的通俗解释 一、NP-Hard 问题是什么意思&#xff1f; 什么是 NP&#xff1f; NP 问题可以理解为「检查答案很容易&#xff0c;但找到答案很难」。 举个例子&#xff1a; 假设你在一个迷宫里&#xff0c;…

【黑灰产】假钱包推广套路

假钱包推广产业链研究 市面上钱包的主要推广方式&#xff1a; 1&#xff0c;竞价&#xff08;搜索引擎&#xff09;&#xff0c;误导客户为真正官方钱包从而完成下载使用 优点&#xff1a;精准&#xff0c;客户大 缺点&#xff1a;竞价户容易挂&#xff0c;投资大 2&#xff0…

C#范围表达式,模式匹配,逆变和协变--11

目录 一.范围表达式 1.概述 2.语法 3.代码示例 4.实现原理 5.应用场景 二.模式匹配 1.概述 2.核心概念 3.常用模式类型 4.Switch表达式 5.使用示例 6.优势 三.逆变和协变 1.概述 2.泛型类型参数的变性 3.协变示例 4.逆变示例 5.注意事项 6.应用场景 总结 一…

ollama教程(window系统)

前言 在《本地大模型工具哪家强&#xff1f;对比Ollama、LocalLLM、LM Studio》一文中对比了三个常用的大模型聚合工具优缺点&#xff0c;本文将详细介绍在window操作系统下ollama的安装和使用。要在 Windows 上安装并使用 Ollama&#xff0c;需要依赖 NVIDIA 显卡&#xff0c…

代码随想录 字符串 test1

344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; 依次交换首尾。 class Solution { public:void reverseString(vector<char>& s) {int h,l;//首尾指针for(h 0, l s.size() - 1; h < s.size() / 2; h, l--){swap(s[h], s[l]);}return ;} };

[论文阅读] (35)TIFS24 MEGR-APT:基于攻击表示学习的高效内存APT猎杀系统

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…