• 本文介绍C++ 并发中使用的其他类型的锁,包括unique_lock,shared_lock, 以及recursive_lock等。shared_lock和unique_lock比较常用,而recursive_lock用的不多,或尽可能规避用这种锁。

unique_lock

  • unique_lock和lock_guard基本用法相同,构造时默认加锁,析构时默认解锁,但unique_lock有个好处就是可以手动解锁。这一点尤为重要,方便我们控制锁住区域的粒度(加锁的范围大小),也能支持和条件变量配套使用,至于条件变量我们之后再介绍,本文主要介绍锁的相关操作。

#include<iostream>
#include<mutex>
#include<stack>
#include<memory>
#include<thread>
using std::cout;
using std::endl;//unique_lock   开始
std::mutex mtx;
int shared_data = 0;
void use_unique() {//lock可自动解锁,也可手动解锁std::unique_lock<std::mutex> lock(mtx);std::cout << "lock success" << std::endl;shared_data++;lock.unlock();
}// 我们可以通过unique_lock的owns_lock判断是否持有锁
//可判断是否占有锁
void owns_lock() {//lock可自动解锁,也可手动解锁std::unique_lock<std::mutex> lock(mtx);shared_data++;if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;     // True}else {std::cout << "doesn't own lock" << std::endl;}lock.unlock();if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;}else {std::cout << "doesn't own lock" << std::endl;  // True}
}//unique_lock可以延迟加锁
//可以延迟加锁
void defer_lock() {//延迟加锁std::unique_lock<std::mutex> lock(mtx, std::defer_lock);if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;     // False}if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;}else {std::cout << "doesn't own lock" << std::endl;  // True}//可以加锁lock.lock();//可以自动析构解锁,也可以手动解锁lock.unlock();
}//unique_lock   结束
int main()
{//use_unique();//owns_lock();defer_lock();return 0;
}
  • 整合运用
#include<iostream>
#include<mutex>
#include<stack>
#include<memory>
#include<thread>
using std::cout;
using std::endl;//unique_lock   开始
std::mutex mtx;
int shared_data = 0;
void use_unique() {//lock可自动解锁,也可手动解锁std::unique_lock<std::mutex> lock(mtx);std::cout << "lock success" << std::endl;shared_data++;lock.unlock();
}// 我们可以通过unique_lock的owns_lock判断是否持有锁
//可判断是否占有锁
void owns_lock() {//lock可自动解锁,也可手动解锁std::unique_lock<std::mutex> lock(mtx);shared_data++;if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;     // True}else {std::cout << "doesn't own lock" << std::endl;}lock.unlock();if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;}else {std::cout << "doesn't own lock" << std::endl;  // True}
}//unique_lock可以延迟加锁
//可以延迟加锁
void defer_lock() {//延迟加锁std::unique_lock<std::mutex> lock(mtx, std::defer_lock);if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;     // False}if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;}else {std::cout << "doesn't own lock" << std::endl;  // True}//可以加锁lock.lock();//可以自动析构解锁,也可以手动解锁lock.unlock();
}//同时使用owns和defer
void use_own_defer() {std::unique_lock<std::mutex>  lock(mtx);// 判断是否拥有锁if (lock.owns_lock()){std::cout << "Main thread has the lock." << std::endl; // True}else{std::cout << "Main thread does not have the lock." << std::endl;}std::thread t([]() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock);// 判断是否拥有锁if (lock.owns_lock()){std::cout << "Thread has the lock." << std::endl;}else{std::cout << "Thread does not have the lock." << std::endl;// True}// 加锁lock.lock();// 判断是否拥有锁if (lock.owns_lock()){std::cout << "Thread has the lock." << std::endl; // // True}else{std::cout << "Thread does not have the lock." << std::endl;}// 解锁lock.unlock();});lock.unlock();t.join();
}
//unique_lock   结束
int main()
{//use_unique();//owns_lock();//defer_lock();use_own_defer();return 0;
}
  • 和lock_guard一样,unique_lock也支持领养锁

//同样支持领养操作
void use_own_adopt() {mtx.lock();std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);if (lock.owns_lock()) {std::cout << "owns lock" << std::endl;}else {std::cout << "does not have the lock" << std::endl;}lock.unlock();
}
  • 尽管是领养的,但是打印还是会出现owns lock,因为不管如何锁被加上,就会输出owns lock。既然unique_lock支持领养操作也支持延迟加锁,那么可以用两种方式实现前文lock_guard实现的swap操作。

//之前的交换代码可以可以用如下方式等价实现
int a = 10;
int b = 99;
std::mutex  mtx1;
std::mutex  mtx2;
void safe_swap() {std::lock(mtx1, mtx2);std::unique_lock<std::mutex> lock1(mtx1, std::adopt_lock);std::unique_lock<std::mutex> lock2(mtx2, std::adopt_lock);std::swap(a, b);//错误用法//mtx1.unlock();//mtx2.unlock();
}
void safe_swap2() {std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);//需用lock1,lock2加锁std::lock(lock1, lock2);//错误用法//std::lock(mtx1, mtx2);std::swap(a, b);
}
  • 大家注意一旦mutex被unique_lock管理,加锁和释放的操作就交给unique_lock,不能调用mutex加锁和解锁,因为锁的使用权已经交给unique_lock了。我们知道mutex是不支持移动和拷贝的,但是unique_lock支持移动,当一个mutex被转移给unique_lock后,可以通过unique_ptr转移其归属权.
    注意延迟加锁和领养锁之后,就只能使用后者进行解锁了,不能使用原生的锁进行解锁操作

/转移互斥量所有权
//互斥量本身不支持move操作,但是unique_lock支持
std::unique_lock <std::mutex>  get_lock() {std::unique_lock<std::mutex>  lock(mtx);shared_data++;return lock;
}
void use_return() {std::unique_lock<std::mutex> lock(get_lock());shared_data++;
}

锁的粒度表示加锁的精细程度,一个锁的粒度要足够大,保证可以锁住要访问的共享数据。同时一个锁的粒度要足够小,保证非共享数据不被锁住影响性能。而unique_ptr则很好的支持手动解锁。

void precision_lock() {std::unique_lock<std::mutex> lock(mtx);shared_data++;lock.unlock();//不设计共享数据的耗时操作不要放在锁内执行std::this_thread::sleep_for(std::chrono::seconds(1));lock.lock();shared_data++;
}

共享锁

  • 试想这样一个场景,对于一个DNS服务,我们可以根据域名查询服务对应的ip地址,它很久才更新一次,比如新增记录,删除记录或者更新记录等。平时大部分时间都是提供给外部查询,对于查询操作,即使多个线程并发查询不加锁也不会有问题,但是当有线程修改DNS服务的ip记录或者增减记录时,其他线程不能查询,需等待修改完再查询。或者等待查询完,线程才能修改。也就是说读操作并不是互斥的,同一时间可以有多个线程同时读,但是写和读是互斥的,写与写是互斥的,简而言之,写操作需要独占锁。而读操作需要共享锁。

  • 要想使用共享锁,需使用共享互斥量std::shared_mutex,std::shared_mutex是C++17标准提出的。C++14标准可以使用std::shared_time_mutex,

  • std::shared_mutex 和 std::shared_timed_mutex 都是用于实现多线程并发访问共享数据的互斥锁,但它们之间存在一些区别:

  • std::shared_mutex:

    • 提供了 lock(), try_lock(), 和 try_lock_for() 以及 try_lock_until() 函数,这些函数都可以用于获 取互斥锁。
    • 提供了 try_lock_shared()lock_shared() 函数,这些函数可以用于获取共享锁。
    • std::shared_mutex 被锁定后,其他尝试获取该锁的线程将会被阻塞,直到该锁被解锁。
  • std::shared_timed_mutex:

    • std::shared_mutex 类似,也提供了 lock(), try_lock(), 和 try_lock_for() 以及 try_lock_until() 函数用于获取互斥锁。
    • std::shared_mutex 不同的是,它还提供了 try_lock_shared()lock_shared() 函数用于获取共享锁,这些函数在尝试获取共享锁时具有超时机制。
    • std::shared_timed_mutex 被锁定后,其他尝试获取该锁的线程将会被阻塞,直到该锁被解锁,这与 std::shared_mutex 相同。然而,当尝试获取共享锁时,如果不能立即获得锁,std::shared_timed_mutex 会设置一个超时,超时过后如果仍然没有获取到锁,则操作将返回失败。

C++11标准没有共享互斥量,可以使用boost提供的boost::shared_mutex。如果我们想构造共享锁,可以使用std::shared_lock,如果我们想构造独占锁, 可以使用std::lock_gurad.我们用一个类DNService代表DNS服务,查询操作使用共享锁,而写操作使用独占锁,可以是如下方式的。

在这里插入图片描述


#include <iostream>
#include <memory>
#include<mutex>
#include<thread>
#include<map>
#include <shared_mutex> // std::shared_mutex(C++17引入)   std::shared_lock(C++14引入)
class DNService {
public:DNService() {}//读操作采用共享锁std::string QueryDNS(std::string dnsname) {std::shared_lock<std::shared_mutex> shared_locks(_shared_mtx);auto iter = _dns_info.find(dnsname);if (iter != _dns_info.end()) {return iter->second;}return "";}//写操作采用独占锁void AddDNSInfo(std::string dnsname, std::string dnsentry) {std::lock_guard<std::shared_mutex>  guard_locks(_shared_mtx);_dns_info.insert(std::make_pair(dnsname, dnsentry));}
private:std::map<std::string, std::string> _dns_info;mutable std::shared_mutex  _shared_mtx;
};

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

递归锁

有时候我们在实现接口的时候内部加锁,接口内部调用完结束自动解锁。会出现一个接口调用另一个接口的情况,如果用普通的std::mutex就会出现卡死,因为嵌套加锁导致卡死。


#include <iostream>
#include <mutex>
#include <thread>class BankAccount {
private:std::mutex mtx;double balance = 1000.0;public:// 接口1:存款void deposit(double amount) {std::lock_guard<std::mutex> lock(mtx);balance += amount;std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;}// 接口2:取款void withdraw(double amount) {std::lock_guard<std::mutex> lock(mtx);if (balance >= amount) {balance -= amount;std::cout << "Withdrew " << amount << ", new balance: " << balance << std::endl;} else {std::cout << "Insufficient funds for withdrawal of " << amount << std::endl;}}// 接口3:转账(会调用存款和取款接口)void transfer(BankAccount& toAccount, double amount) {std::lock_guard<std::mutex> lock(mtx);  // 第一次加锁this->withdraw(amount);  // 内部再次尝试加锁 - 这里会导致死锁!toAccount.deposit(amount);}
};int main() {BankAccount accountA;BankAccount accountB;// 这个调用会导致死锁accountA.transfer(accountB, 100.0);return 0;
}

在这里插入图片描述

但是我们可以使用递归锁。


#include <iostream>
#include <mutex>class BankAccount {
private:std::recursive_mutex mtx;  // 关键修改:使用可重入锁double balance = 1000.0;public:void deposit(double amount) {std::lock_guard<std::recursive_mutex> lock(mtx);balance += amount;std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;}void withdraw(double amount) {std::lock_guard<std::recursive_mutex> lock(mtx);if (balance >= amount) {balance -= amount;std::cout << "Withdrew " << amount << ", new balance: " << balance << std::endl;}else {std::cout << "Insufficient funds for withdrawal of " << amount << std::endl;}}void transfer(BankAccount& toAccount, double amount) {std::lock_guard<std::recursive_mutex> lock(mtx);  // 第一次加锁this->withdraw(amount);  // 内部再次加锁 - 现在可以正常工作toAccount.deposit(amount);}
};int main() {BankAccount accountA;BankAccount accountB;accountA.transfer(accountB, 100.0);return 0;
}

但我个人并不推荐递归锁,可以从设计源头规避嵌套加锁的情况,我们可以将接口相同的功能抽象出来,统一加锁。下面的设计演示了如何使用递归锁
//不推荐采用递归锁,使用递归锁说明设计思路并不理想,需优化设计
//推荐拆分逻辑,将共有逻辑拆分为统一接口

#include <iostream>
#include <mutex>class BankAccount {
private:std::mutex mtx;  // 使用普通互斥量double balance = 1000.0;// 不加锁的核心实现方法void unsafe_deposit(double amount) {balance += amount;std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;}// 不加锁的核心实现方法bool unsafe_withdraw(double amount) {if (balance >= amount) {balance -= amount;std::cout << "Withdrew " << amount << ", new balance: " << balance << std::endl;return true;}std::cout << "Insufficient funds for withdrawal of " << amount << std::endl;return false;}public:// 线程安全的公共接口void deposit(double amount) {std::lock_guard<std::mutex> lock(mtx);unsafe_deposit(amount);}void withdraw(double amount) {std::lock_guard<std::mutex> lock(mtx);unsafe_withdraw(amount);}void transfer(BankAccount& toAccount, double amount) {// 同时锁定两个账户,避免死锁std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);std::unique_lock<std::mutex> lock2(toAccount.mtx, std::defer_lock);std::lock(lock1, lock2);  // 原子化加锁if (unsafe_withdraw(amount)) {toAccount.unsafe_deposit(amount);std::cout << "Transferred " << amount << " successfully" << std::endl;}}
};
int main() {BankAccount accountA;BankAccount accountB;// 这个调用会导致死锁accountA.transfer(accountB, 100.0);return 0;
}

在这里插入图片描述

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

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

相关文章

stm32 freertos下基于 hal库的模拟I2C驱动实现

一 前言 最近搞了两个项目&#xff0c;调了一版freertos下基于hal库得模拟I2C驱动&#xff0c;非常实用&#xff0c;直接拷贝就能用&#xff0c;这里做下记录&#xff0c;主要用到如下四个文件&#xff1a; delay.cdelay.hi2cc.ci2cc.h 二 代码实现 delay.c #include "…

文心大模型 4.5 系列开源首发:技术深度解析与应用指南

文心大模型 4.5 系列开源首发&#xff1a;技术深度解析与应用指南 2025 年 6 月 30 日&#xff0c;百度正式在国内领先的开源平台 GitCode 发布文心大模型 4.5 系列开源模型。此次开源不仅覆盖了从数亿到数百亿参数的多种规模&#xff0c;还在多模态理解、指令遵循、世界知识记…

智能制造——58页智慧工厂解决方案【附全文阅读】

适应人群为制造业企业管理者、技术人员及规划改造团队。主要内容是以 JetLinks IoT 平台为基础&#xff0c;构建涵盖设备层、网络层、平台层和应用层的智慧工厂体系&#xff0c;通过多种通信协议实现设备互联&#xff0c;利用大数据、AI 等技术实现数据采集与处理、设备全生命周…

DeepSeek16-open-webui Pipelines开发填坑

一、虚拟环境安装 mkdir open_webui_pipelines cd open_webui_pipelines python -m venv py3119_env call py3119_env\Scripts\activate 二、下载服务以及安装依赖 git clone https://github.com/open-webui/pipelines.git cd pipelines pip install -r requirements.txt三、…

兰亭妙微APP 界面设计与软件开发综合服务商

兰亭妙微作为 APP 界面设计公司与软件开发公司&#xff0c;自 2011 年成立以来&#xff0c;由清华团队主导&#xff0c;在软件和互联网 UI/UE 设计开发领域发展 16 年&#xff0c;积累了一定的行业经验。 在 APP 界面设计方面&#xff0c;我们的团队包含交互设计师、用户体验相…

【算法】动态规划:1137. 第 N 个泰波那契数

1137. 第 N 个泰波那契数 简单 相关标签 premium lock icon 相关企业 提示 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 示例 1&#xff1a; 输入&am…

[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的校园家教兼职信息交流平台管理系统,推荐!

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本校园家教兼职信息交流平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的…

vue-33(实践练习:使用 Nuxt.js 和 SSR 构建一个简单的博客)

实践练习:使用 Nuxt.js 和 SSR 构建一个简单的博客 使用 Nuxt.js 和 SSR 构建一个简单的博客是巩固你对服务器端渲染理解以及 Nuxt.js 如何简化这一过程的好方法。这个练习将带你完成设置基本博客结构、获取数据并以用户友好的格式展示,同时利用 SSR 的优势来提升 SEO 和性能…

如何在 .Net 7 中使用 MQTT 客户端

介绍 MQTT&#xff08;消息队列遥测传输&#xff09;是一种轻量级消息传递协议&#xff0c;专为资源受限的环境而设计。MQTT 广泛应用于物联网 (IoT) 和机器对机器 (M2M) 通信。 本文将讨论如何在 .NET 7 中实现 MQTT 消费者。我们将使用 MQTTnet 库&#xff0c;这是 C# 中的高…

云上攻防—Docker安全容器逃逸特权模式危险挂载

前言 之前分享的是云服务安全&#xff0c;今天开始云原生安全&#xff0c;安全道路依旧很长。 什么是Docker呢&#xff0c;它是开源的容器化平台&#xff0c;用于开发、部署和运行应用程序。它通过将应用程序及其依赖项打包在轻量级的容器中&#xff0c;实现环境一致性、快速…

2025API 开发工具Apipost 与 Apifox深度对比

在当今数字化时代&#xff0c;API 开发是构建各类软件应用的关键环节。Apipost 和 Apifox 作为两款知名的 API 开发工具&#xff0c;它们在实际开发场景中表现究竟如何呢&#xff1f;接下来&#xff0c;让我们从多个功能点进行深入对比。 一、API 设计功能 接口定义与参数设置…

从零开始搭建Windows AI开发环境:QWQ-32B部署+Cursor插件优化实战

文章目录 前言1.安装Ollama2.QwQ-32B模型安装与运行3.Cursor安装与配置4. 简单使用测试5. 调用本地大模型6. 安装内网穿透7. 配置固定公网地址总结 前言 本方案提出了一种基于Windows系统的智能化开发平台搭建策略&#xff0c;通过融合Cursor智能编程平台、Ollama模型运行框架…

PostgreSQL 中,若需显示 不在 `IN` 子句列表中的数据

在 PostgreSQL 中&#xff0c;若需显示 不在 IN 子句列表中的数据&#xff0c;可以通过以下方法实现&#xff1a; 方法 1&#xff1a;使用 NOT IN&#xff08;注意 NULL 值&#xff09; 直接筛选不包含在 IN 列表中的记录&#xff1a; SELECT * FROM your_table WHERE your_c…

嘉讯科技:医疗信息化、数字化、智能化三者之间的关系和区别

随着技术的不断发展&#xff0c;医疗行业也在发生着巨大的变化。在这个过程中&#xff0c;医疗信息化、数字化、智能化成为三个重要方向。这些变化不仅带来了医疗技术的进步&#xff0c;而且大大提高了医疗服务的质量和效率。 一、医疗信息化 医疗信息化是指医疗行业应用信息技…

Windows VMWare Centos Docker部署Springboot应用

接上篇文章&#xff1a;Windows VMWare Centos环境下安装Docker并配置MySql-CSDN博客文章浏览阅读370次&#xff0c;点赞3次&#xff0c;收藏4次。Windows VMWare Centos环境下安装Docker并配置MySqlhttps://blog.csdn.net/u013224722/article/details/148928081 一、新建Sprin…

JavaEE-Spring事务和事务的传播机制

事务 什么是事务 事务是⼀组操作的集合, 是⼀个不可分割的操作. 事务会把所有的操作作为⼀个整体, ⼀起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功, 要么同时失败. 为什么需要事务? 事务的操作 Spring 中事务的实现 创建好数据库后就是配置数据库相关的配…

共享经济视域下社群经济的本质重构:基于开源AI智能名片链动2+1模式S2B2C商城小程序源码的实证研究

摘要&#xff1a;社群经济在互联网时代呈现爆发式增长&#xff0c;但传统社群运营存在情感维系成本高、商业转化路径长、技术赋能不足等痛点。本文以共享经济理论为框架&#xff0c;结合开源AI智能名片链动21模式S2B2C商城小程序源码的技术实践&#xff0c;提出“思想-资源-机会…

测试方法的分类

静态测试 核心分类依据&#xff1a;根据是否执行程序分为静态测试和动态测试 静态测试方法 执行特征&#xff1a;不运行被测程序&#xff0c;通过人工检查或工具分析进行测试 测试对象&#xff1a;主要针对文档&#xff08;包括需求文档、设计文档等&#xff09;和源代码 实…

查看CPU支持的指令集和特性

1&#xff09;gcc -c -Q -marchnative --helptarget 2&#xff09;结果 The following options are target specific: -m128bit-long-double [enabled] -m16 [disabled] -m32 [disabled…

【大模型应用开发】Unity结合大模型实现智能问答功能

零、最终效果 Unity结合大模型实现智能问答功能 一、文本自动换行效果 新建一个Text文本&#xff0c;设置文本的最大宽度 然后添加Content Size Fitter组件&#xff0c;Vertical Fit选择Preferred Size 二、背景随文本长度变化效果 新建一个Image作为文本的背景&#xff0…