中国建设银行网站网上业务服务范围/属于网络营销特点的是
用于共享数据保护的替代工具
虽然互斥元是最通用的机制,但提到保护共享数据时,它们并不是唯一的选择;还有别的替代品,可以在特定情况下提供更恰当的保护。
一个特别极端(但却相当常见)的情况,就是共享数据只在初始化时才需要并发访问的保护,但在那之后却不需要显式同步。这可能是因为数据是一经创建就是只读的,所以就不存在可能的同步问题,或者是因为必要的保护作为数据上操作的一部分被隐式地执行。在任一情况中,在数据被初始化之后锁定互斥元,纯粹是为了保护初始化,这是不必要的,并且对性能会产生的不必要的打击。为了这个原因,C++标准提供了一种机制,纯粹为了在初始化过程中保护共享数据。
在初始化时保护共享数据
假设你有一个构造起来非常昂贵的共享资源,只有当实际需要时你才会要这样做。也许,它会打开一个数据库连接或分配大量的内存。像这样的延迟初始化(lazyinitialization)在单线程代码中是很常见的——每个请求资源的操作首先检查它是否已经初始化,如果没有就在使用之前初始化之。
std::shared_ptr<some_resource> resource_ptr;
void foo()
{if(!resource_ptr){resource_ptr.reset(new some_resource); //❶}resource_ptr->do_something();
}
如果共享资源本身对于并发访问是安全的,当将其转换为多线程代码时唯一需要保护的部分就是初始化❶,但是像清单3.11中这样的朴素的转换,会引起使用该资源的线程产生不必要的序列化。这是因为每个线程都必须等待互斥元,以检查资源是否已经被初始化。
//清单3.11 使用互斥元进行线程安全的延迟初始化
#include <memory>
#include <mutex>struct some_resource
{void do_something(){}};std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo()
{std::unique_lock<std::mutex> lk(resource_mutex); //所有的线程在这里被序列化if(!resource_ptr){resource_ptr.reset(new some_resource); //只有初始化需要被保护}lk.unlock();resource_ptr->do_something();
}int main()
{foo();
}
这段代码是很常见的,不必要的序列化问题已足够大,以至于许多人都试图想出一个更好的方法来实现,包括臭名昭著的二次检查锁定(Double-Checked Locking)模式,在不获取锁❶(在下面的代码中)的情况下首次读取指针,并仅当此指针为NULL时获得该锁。一旦已经获取了锁,该指针要被再次检查❷(这就是二次检查的部分),以防止在首次检查和这个线程获取锁之间,另一个线程就已经完成了初始化。
void undefined_behaviour_with_double_checked_locking()
{if(!resource_ptr) //❶{ std::lock_guard<std::mutex> lk(resource_mutex);if(!resource_ptr) //❷{resource_ptr.reset(new some_resource); //❸}}resource_ptr->do_something(); //❹
}
不幸的是,这种模式因某个原因而臭名昭著。它有可能产生恶劣的竞争条件,因为在锁外部的读取❶与锁内部由另一线程完成的写入不同步❸。这就因此创建了一个竞争条件,不仅涵盖了指针本身,还涵盖了指向的对象。就算一个线程看到另一个线程写入的指针,它也可能无法看到新创建的 some_resource 实例,从而导致do_something()❹的调用在不正确的值上运行。这是一个竞争条件的例子,该类型的竞争条件被C++标准定义为数据竞争(data race),因此被定为未定义行为。因此,这是肯定需要避免的。
C++标准委员会也发现这是一个重要的场景,所以C++标准库提供了std::once_flag和 std::call_once 来处理这种情况。与其锁定互斥元并且显式地检查指针,还不如每个线程都可以使用std::call_once,到 std::call_once返回时,指针将会被某个线程初始化(以完全同步的方式),这样就安全了。使用std::call_once比显式使用互斥元通常会有更低的开销,特别是初始化已经完成的时候,所以在std::call_once符合所要求的功能时应优先使用之。下面的例子展示了与清单3.11相同的操作,改写为使用std::call_once。在这种情况下,通过调用函数来完成初始化,但是通过一个带有函数调用操作符的类实例也可以很容易地完成初始化。与标准库中接受函数或者断言作为参数的大部分函数类似,std::call_once可以与任意函数或可调用对象合作。
std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag; //❶void int_resource()
{resource_ptr.reset(new some_resource);
}
void foo()
{std::call_once(resource_flag, init_resource); //初始化会被正好调用一次resource_ptr->do_something();
}
在这个例子中,std::once_flag❶和被初始化的数据都是命名空间作用域的对象,但是std::call_once()可以容易地用于类成员的延迟初始化,如清单3.12所示。
//清单3.12 使用std::call_one的线程安全的类成员延迟初始化
#include <mutex>struct connection_info
{};struct data_packet
{};struct connection_handle
{void send_data(data_packet const&){}data_packet receive_data(){return data_packet();}
};struct remote_connection_manager
{connection_handle open(connection_info const&){return connection_handle();}
} connection_manager;class X
{
private:connection_info connection_details;connection_handle connection;std::once_flag connection_init_flag;void open_connection(){connection=connection_manager.open(connection_details);}
public:X(connection_info const& connection_details_):connection_details(connection_details_){}void send_data(data_packet const& data) //❶{std::call_once(connection_init_flag,&X::open_connection,this); //❷connection.send_data(data);}data_packet receive_data() //❸{std::call_once(connection_init_flag,&X::open_connection,this);return connection.receive_data();}
};int main()
{}
在这个例子中,初始化由首次调用 send_data()❶或是由首次调用receive_data()来完成。使用成员函数open_connection()来初始化数据,同样需要将this指针传入函数。和标准库中其他接受可调用对象的函数一样,比如std::thread 和std::bind()的构造函数,这是通过传递一个额外的参数给std::call_once()来完成的❷。
值得注意的是,像std::mutex、std::once_flag的实例是不能被复制或移动的,所以如果想要像这样把它们作为类成员来使用,就必须显式定义这些你所需要的特殊成员函数。
一个在初始化过程中可能会有竞争条件的场景,是将局部变量声明为static的。这种变量的初始化,被定义为在时间控制首次经过其声明时发生。对于多个调用该函数的线程,这意味着可能会有针对定义“首次”的竞争条件。在许多C++11之前的编译器上,这个竞争条件在实践中是有问题的,因为多个线程可能都认为它们是第一个,并试图去初始化该变量,又或者线程可能会在初始化已在另一个线程上启动但尚未完成之时试图使用它。在C++11中,这个问题得到了解决。初始化被定义为只发生在一个线程上,并且其他线程不可以继续直到初始化完成,所以竞争条件仅仅在于哪个线程会执行初始化,而不会有更多别的问题。对于需要单一全局实例的场合,这可以用作std::call_once的替代品。
class my_class;
my_class& get_my_class_instance()
{static my_class instance; //❶初始化保证线程是安全的return instance;
}
多个线程可以继续安全地调用get_my_class_instance()❶,而不必担心初始化时的竞争条件。
保护仅用于初始化的数据是更普遍的场景下的一个特例,那些很少更新的数据结构。对于大多数时间而言,这样的数据结构是只读的,因而可以毫无顾忌地被多个线程同时读取,但是数据结构偶尔可能需要更新。这里我们所需要的是一种承认这一事实的保护机制。
保护很少更新的数据结构
假设有一个用于存储DNS条目缓存的表,它用来将域名解析为相应的P地址。通常,一个给定的DNS条目将在很长一段时间里保持不变——在许多情况下,DNS条目会保持数年不变。虽然随着用户访问不同的网站,新的条目可能会被不时地添加到表中,但这一数据却将在其整个生命中基本保持不变。定期检查缓存条目的有效性是很重要的,但是只有在细节已有实际改变的时候才会需要更新。
虽然更新是罕见的,但它们仍然会发生,并且如果这个缓存可以从多个线程访问,它就需要在更新过程中进行适当的保护,以确保所有线程在读取缓存时都不会看到损坏的数据结构。
在缺乏完全符合预期用法并且为并发更新与读取专门设计的专用数据结构的情况下,这种更新要求线程在进行更新时独占访问数据结构,直到它完成了操作。一旦更新完成,该数据结构对于多线程并发访问又是安全的了。使用std::mutex来保护数据结构就因而显得过于悲观,因为这会在数据结构没有进行修改时消除并发读取数据结构的可能,我们需要的是另一种互斥元。这种新的互斥元通常称为读写(reader-writer)互斥元,因为它考虑到了两种不同的用法:由单个“写”线程独占访问或共享,由多个“读”线程并发访问。
新的C++标准库并没有直接提供这样的互斥元,尽管已向标准委员会提议。由于这个建议未被接纳,本节中的例子使用由Boost库提供的实现,它是基于这个建议的。在后面你会看到,使用这样的互斥元并不是万能药,性能依赖于处理器的数量以及读线程和更新线程的相对工作负载。因此,分析代码在目标系统上的性能是很重要的,以确保额外的复杂度会有实际的收益。
你可以使用 boost::shared_mutex的实例来实现同步,而不是使用std::mutex的实例。对于更新操作,std::lock_guard<boost::shared_mutex>
和std::unique_lock<boost::shared_mutex>
可用于锁定,以取代相应的std::mutex特化。这确保了独占访问,就像std::mutex那样。那些不需要更新数据结构的线程能够转而使用boost::shared_lock<boost::shared_mutex>
来获得共享访问。这与std::unique_lock用起来正是相同的,除了多个线程在同一时间、同一boost::share_mutex上可能会具有共享锁。唯一的限制是,如果任意一个线程拥有一个共享锁,试图获取独占锁的线程会被阻塞,直到其他线程全都撤回它们的锁,同样地,如果任意一个线程具有独占锁,其他线程都不能获取共享锁或独占锁,直到第一个线程撤回了它的锁。
清单3.13展示了一个简单的如前面所描述的DNS缓存,使用std::map来保存缓存数据,用boost::share_mutex进行保护。
//清单3.13 使用boost::share_mutex保护数据结构
#include <map>
#include <string>
#include <mutex>
#include <boost/thread/shared_mutex.hpp>class dns_entry
{};class dns_cache
{std::map<std::string,dns_entry> entries;boost::shared_mutex entry_mutex;
public:dns_entry find_entry(std::string const& domain){boost::shared_lock<boost::shared_mutex> lk(entry_mutex); //❶std::map<std::string,dns_entry>::const_iterator const it=entries.find(domain);return (it==entries.end())?dns_entry():it->second;}void update_or_add_entry(std::string const& domain,dns_entry const& dns_details){std::lock_guard<boost::shared_mutex> lk(entry_mutex); //❷entries[domain]=dns_details;}
};int main()
{}
在清单3.13中,find_entry()使用一个boost::share_lock<>
实例来保护它,以供共享、只读的访问❶:多个线程因而可以毫无问题地同时调用find_entry()。另一方面,update_or_add_entry()使用一个 std::lock_guard<>
实例,在表被更新时提供独占访问❷;不仅在调用update_or_add_entry()中其他线程被阻止进行更新,调用find_entry()的线程也会被阻塞。
递归锁
在使用std::mutex的情况下,一个线程试图锁定其已经拥有的互斥元是错误的,并且试图这么做将导致未定义行为(undefined behavior)。然而,在某些情况下,线程多次重新获取同一个互斥元却无需事先释放它是可取的。为了这个目的,C++标准库提供了std::recursive_mutex。它就像std::mutex一样,区别在于你可以在同一个线程中的单个实例上获取多个锁。在互斥元能够被另一个线程锁定之前,你必须释放所有的锁,因此如果你调用lock()三次,你必须也调用unlock()三次。正确使用std::lock_guard<std::recursive_mutex>
和std::unique_lock<std::recursive_mutex>
将会为你处理。
大多数时间,如果你觉得需要一个递归互斥元,你可能反而需要改变你的设计。递归互斥元常用在一个类被设计成多个线程并发访问的情况中,因此它具有一个互斥元来保护成员数据。每个公共成员函数锁定互斥元,进行工作,然后解锁互斥元。然而,有时一个公共成员函数调用另一个函数作为其操作的一部分是可取的。在这种情况下,第二个成员函数也将尝试锁定该互斥元,从而导致未定义行为。粗制滥造的解决方案,就是将互斥元改为递归互斥元。这将允许在第二个成员函数中对互斥元的锁定成功进行,并且函数继续。
然而,这样的用法是不推荐的,因为它可能导致草率的想法和糟糕的设计。特别地,类的不变量在锁被持有时通常是损坏的,这意味着第二个成员函数需要工作,即便在被调用时使用的是损坏的不变量。通常最好是提取一个新的私有成员函数,该函数是从这两个成员函数中调用的,它不锁定互斥元(它认为互斥元已经被锁定)。然后,你可以仔细想想在什么情况下可以调用这个新函数以及在那些情况下数据的状态。
相关文章:

3.3用于共享数据保护的替代工具
用于共享数据保护的替代工具 虽然互斥元是最通用的机制,但提到保护共享数据时,它们并不是唯一的选择;还有别的替代品,可以在特定情况下提供更恰当的保护。 一个特别极端(但却相当常见)的情况,…...

探索数据之美:初步学习 Python 柱状图绘制
文章目录 一 基础柱状图1.1 创建简单柱状图1.2 反转x和y轴1.3 数值标签在右侧1.4 演示结果 二 基础时间线柱状图2.1 创建时间线2.2 时间线主题设置取值表2.3 演示结果 三 GDP动态柱状图绘制3.1 需求分析3.2 数据文件内容3.3 列表排序方法3.4 参考代码3.5 运行结果 一 基础柱状图…...

647. 回文子串
boolean默认类型是false class Solution {public int countSubstrings(String s) {if(s null) return 0;int result 0;int length s.length();boolean[][] dp new boolean[length][length];for(int j0;j<length;j){for(int i0;i<j;i){if(s.charAt(i) s.charAt(j)){i…...

cmake (更新中)
概述 关于 CMake CMake 是一个可扩展的开源系统,以一种与操作系统和编译器无关的方式来管理构建过程。与许多跨平台系统不同,CMake 被设计为与本机构建环境配合使用。在每个源代码目录中放置简单的配置文件(称为 CMakeLists.txt 文件…...

【go语言基础】指针数组和数组指针
1.概念 (1)指针数组: 存储指针的数组,也叫存储地址的数组,简单说就是存储地址的。 首先它是一个数组,数组中的元素都是指针(地址)。 (2)数组指针…...

ModaHub魔搭社区——Milvus Cloud向量数据库
向量数据库:在AI时代的快速发展与应用 摘要: 随着人工智能技术的不断进步,向量数据库在处理大规模数据方面发挥着越来越重要的作用。本文介绍了向量数据库的基本概念、应用场景和技术挑战,并详细阐述了Milvus Cloud作为典型的向量数据库产品的技术特点、性能优化和应用案例…...

【Java】常用Stream API
常见 Stream 流表达式 总体结构图 一、两大类型 中间操作(Intermediate Operations) 中间操作是指在Stream上执行的操作, 它们返回一个新的Stream, 允许你链式地进行多个中间操作. 终端操作(Terminal Operations) 对Stream进行最终处理的操作, 当调用终端操作时, Stream会开始执…...

P1941 [NOIP2014 提高组] 飞扬的小鸟
代码部分前有一千六百字了 P1941 [NOIP2014 提高组] 飞扬的小鸟 考察对背包 dp 算法过程理解的透彻性。过程透彻性也是解决所有问题的关键(建立在算法已学的基础上)。 n , m n,m n,m 的范围足够我们 O ( n m ) O(nm) O(nm) 的遍历整个地图。设 f i , …...

Vue3+Element plus+pageHelper实现分页
安装element plus npm install element-plus --save引入 修改main.js: import { createApp } from vue import App from ./App.vue import ElementPlus from element-plus import element-plus/dist/index.cssconst app createApp(App) app.use(ElementPlus) ap…...
外贸路上那些哭笑不得的事情
前几天一个老顾客在软件上联系,说自己上次的订货体验很满意,货物的质量很好,而且服务和回复也很及时, 比起他之前的供货商要好很多,他之前的供货商虽然货物的质量也很好,但是每次询问问题都是要等好久才给…...

双端列表 —— Deque 接口概述,使用ArrayDeque实现队列和双端队列数据结构
Deque接口简介 Deque译为双端队列,在双向都能作为队列来使用,同时可用作栈。Deque接口的方法是对称成比例的。 Deque接口继承Queue接口,因此具有Queue,Collection,Iterable的方法属性。 双端队列的工作原理 在常规队…...

构建可观测架构,从这5个方面着手
随着系统复杂度的提升,“可观测性”(Observability)成为架构建设的重要原则之一。那么构建一个可观测的系统架构需要做哪些工作呢?本文将从以下5个方面介绍构建可观测架构的主要考虑: 1.定义指标和度量,明确关键业务指标需求 首先要确定核心业务指标,比如请求响应…...

前端面试的性能优化部分(7)每天10个小知识点
目录 系列文章目录前端面试的性能优化部分(1)每天10个小知识点前端面试的性能优化部分(2)每天10个小知识点前端面试的性能优化部分(3)每天10个小知识点前端面试的性能优化部分(4)每天…...

【云原生】kubernetes中容器的资源限制
目录 1 metrics-server 2 指定内存请求和限制 3 指定 CPU 请求和限制 资源限制 在k8s中对于容器资源限制主要分为以下两类: 内存资源限制: 内存请求(request)和内存限制(limit)分配给一个容器。 我们保障容器拥有它请求数量的…...

java Long型数据返回到前端失进度问题解决
直接在springmvc配置中增加信息转换。亲测可用。简单粗暴 Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter jackson2HttpMessageConverter new MappingJackson2HttpMessageCo…...

【设计模式】-策略模式:优雅处理条件逻辑
Java 策略模式之优雅处理条件逻辑 前言 在软件开发中,我们经常会遇到根据不同的条件执行不同逻辑的情况。这时,策略模式是一种常用的设计模式,能够使代码结构清晰、易于扩展和维护。 本文将详细介绍策略模式的概念及其在Java中的应用&#x…...

SpringBoot整合多数据源
SpringBoot整合多数据源 在实际企业项目开发中,我们经常会在SpringBoot项目中配置多数据源,一方面可以减缓数据库压力,另一方面可以也是业务需求的场景 下面就来看看如何在SpringBoot项目中配置多数据源 POM 在配置多数据源之前ÿ…...

CLIP论文精度
CLIP论文精度 Zero-shot CLIP多模态模型 Image Endecoder是一个图片编码器,既可以是ResNet,也可以是Vision Transformer. Text Encoder和Image Encoder产生的两组特征进行对比学习(无监督训练) 分类头?“分类头” 是指网络结…...

LouvainMethod分布式运行的升级之路
1、背景介绍 Louvain是大规模图谱的谱聚类算法,引入模块度的概念分二阶段进行聚类,直到收敛为止。分布式的代码可以在如下网址进行下载。 GitHub - Sotera/spark-distributed-louvain-modularity: Spark / graphX implementation of the distri…...

【Node.js】低代码平台源码
一、低代码简介 低代码管理系统是一种通过可视化界面和简化的开发工具,使非专业开发人员能够快速构建和管理应用程序的系统。它提供了一套预先定义的组件和模块,使用户可以通过拖放操作来设计应用程序的界面和逻辑。低代码管理系统还提供了自动化的工作…...

docker 部署 xxl-job-admin
1、先安装mysql docker pull mysql 2、运行mysql 容器 ( 端口 3306 容器名称 mysql 密码 123456 ) docker run -d --name mysql -e MYSQL_ROOT_PASSWORD123456 -p 3306:3306 mysql 3、将tables_xxl_job.sql文件(官网地址:http…...

c++(空间配置器)[32]
空间配置器 一级空间配置器 || 二级空间配置器 默认先走二级然后判断 二级空间配置器 一个指针指向start_free然后start_free向后移动,相当于哈希桶的头删和头插 8byte:切大补小 C的二级空间配置器按照8字节(或者更大的倍数)切分…...

Linux系列之解压文件
一.欢迎来到我的酒馆 使用命令解压Linux文件。 目录 一.欢迎来到我的酒馆二.使用命令解压文件 二.使用命令解压文件 2.1解压 .tar.gz文件: tar -zxvf 文件名.tar.gz.tar,gz这种文件是tar文件的压缩文件,因此可以使用tar命令进行解压 -zxvf命令参数&…...

为什么重写equals方法时必须重写hashcode方法
与 equals的区别 如果两个引用类型变量使用运算符,那么比较的是地址,它们分别指向的是否是同一地址的对象,结果一定是false,因为两个对象地址必然不同。 不能实现比较对象的值是否相同。 所有对象都有equals方法,默认…...

java导入excel图片处理
直接看代码吧,主要逻辑吧excel的图片拿到 压缩上传获取url // 将文件转成XSSFWorkbook工作簿XSSFWorkbook wb new XSSFWorkbook(uploadFile);// 获取工作薄中第一个excel表格XSSFSheet sheet wb.getSheetAt(0);// 核心:::获取ex…...

【Rust】Rust学习 第四章认识所有权
第四章认识所有权 所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。 4.1 所有权 所有运行的程序都…...

学习C语言第三天 :关系操作符、逻辑操作符
1.关系操作符 C语言用于比较的表达式,称为“关系表达式”里面使用的运算符就称(relationalexpression),为“关系运算符” (relationaloperator) ,主要有下面6个。 > 大于运算符 < 小于运算符 > 大于等于运算符 < 小于等…...

Jenkins自动化打包脚本
一、背景 jenkins可以设置定时任务打包,也已手动点按钮打包,还可以通过执行http请求打包,今天我们就通过shell脚本,通过curl命令进行jenkins打包。 二、步骤 2.1 在jenkins上构建项目 设置触发器 2.2 通过shell脚本触发远程构…...

一百五十、Kettle——Kettle官网下载地址
一、官网地址 Home - Hitachi VantaraThe site home pagehttps://community.hitachivantara.com/docs/DOC-1009855 二、下载地址 Pentaho from Hitachi Vantara download | SourceForge.netDownload Pentaho from Hitachi Vantara for free. End to end data integration and…...
使用 Visual Studio Code 调试 CMake 脚本
之前被引入到 Visual Studio 中的 CMake 调试器,现已在 Visual Studio Code 中可用。 也就是说,现在你可以通过在 VS Code 中安装 CMake 工具扩展,来调试你的 CMakeLists.txt 脚本了。是不是很棒? 背景知识 Visual C 开发团队和 CMake 的维…...