《c++并发编程实战》 笔记
《c++并发编程实战》 笔记
- 1、你好,C++的并发世界
- 为什么要使用并发
- 第2章 线程管理
- 2.1.1 启动线程
- 2.2 向线程函数传递参数
- 2.5 识别线程
- 第3章 线程间共享数据
- 3.2.1 C++中使用互斥量
- 避免死锁的进阶指导
- 保护共享数据的替代设施
- 第4章 同步并发操作
- 4.1 等待一个事件或其他条件
- 4.2 使用期望等待一次性事件
- 第6章 基于锁的并发数据结构设计
- 4.2 使用期望等待一次性事件
1、你好,C++的并发世界
为什么要使用并发
1、并发分离关注点的定义
并发分离关注点主要是指在并发编程中,通过将不同的逻辑或任务分离到不同的线程或进程中执行,从而实现关注点(即程序中需要特别关注或处理的部分)的分离。这种分离有助于简化程序结构,提高代码的可读性和可维护性。
2、使用并发提高性能,可用的方法
- 第一个也是最明显的是将单个任务划分为多个部分并并行运行每个部分,从而减少总运行时间。
- 第二个:数据并行性(Data Parallelism),即同时对多个数据集或数据块执行相同的操作或算法。
初始线程始于main(),而新线程始于hello()。
第2章 线程管理
2.1.1 启动线程
使用C++线程库启动线程,可以归结为构造std::thread对象:
std::thread
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
- Function 是一个可调用对象(如函数指针、lambda 表达式、函数对象等)的类型。
- Args… 是传递给 Function的参数的类型列表。
std::thread 在 C++ 中扮演着核心角色,特别是在多线程编程领域。它的主要作用是提供一种机制来创建和管理线程,使得程序能够并行地执行多个任务。
- 创建新线程:通过 std::thread的构造函数,可以轻松地创建一个新线程来执行指定的函数或可调用对象。这允许开发者将耗时的操作或可以并行处理的任务放到单独的线程中执行,从而提高程序的性能和响应性。
- 管理线程生命周期:std::thread 对象与它所代表的线程紧密相关。通过调用 join() 或 detach()方法,可以管理线程的生命周期。join() 方法会阻塞当前线程,直到由 std::thread 对象表示的线程完成其执行。detach()方法则允许线程独立于 std::thread 对象运行,此时 std::thread对象不再拥有该线程,且无法再与之交互(除了获取其ID)。
线程可连接(joinable)是C++中std::thread对象的一个状态,它表示该std::thread对象代表了一个正在运行或已经启动但尚未结束的线程。当一个std::thread对象被创建并成功启动了一个新线程时,它就进入了可连接状态。
线程资源:
如线程栈、线程控制块等。如果这些资源在std::thread对象被销毁时仍未被释放,就会发生资源泄露。
在C++中,std::thread对象通过join()或detach()方法来管理其代表的线程。如果std::thread对象在销毁时仍然是可连接的(即线程仍在运行),且既未调用join()也未调用detach(),则程序会调用std::terminate()来终止执行,以防止潜在的资源泄露。
std::terminate会终止整个程序,而不是线程。操作系统会在程序终止时回收所有由该程序分配的资源。为了防止程序在不确定的线程状态下继续执行,因为此时线程可能还在访问或操作已经销毁的 std::thread 对象所管理的资源。
std::thread对象通常与特定的执行线程相关联,并且一旦线程执行完毕,std::thread对象就不再拥有任何线程(即变为空)。
线程的状态变化:
-
从非joinable到joinable:
- 当通过调用std::thread的构造函数并传入一个可调用对象(如函数指针、Lambda表达式、绑定表达式等)来创建std::thread对象时,如果构造函数成功,则新创建的线程对象将处于joinable状态。这意呀着你可以对该线程调用join()来等待它完成,或者调用detach()来分离它。
-
从joinable到非joinable:
- 一旦对joinable的线程调用了join()或detach(),该线程对象就不再处于joinable状态。对已经join或detach的线程对象再次调用join()或detach()将导致std::system_error异常。
- 如果线程的执行函数已经返回(即线程已结束),并且你尚未对该线程调用join()或detach(),则尝试对该线程对象调用join(),detach仍然有效,但调用后将使线程对象变为非joinable。
- 如果线程对象被销毁时仍然处于joinable状态(即没有调用join()或detach()),则程序将调用std::terminate()来终止执行,以避免资源泄露。因此,重要的是要确保在销毁std::thread对象之前,要么调用join()要么调用detach()。
2.2 向线程函数传递参数
参数要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问。
一定要使用引用:
std::ref 用于创建一个对给定对象的引用封装器(reference wrapper),这个封装器可以被存储在容器中,或者作为函数参数传递给需要复制语义但实际上需要引用语义的地方。简而言之,std::ref 允许你以引用的方式传递对象,即使是在需要拷贝的上下文中。
std::bind 是 C++ 标准库中的一个功能强大的工具,它定义在头文件 中。std::bind 用于将可调用对象(如函数、函数对象、lambda 表达式、成员函数指针等)与其参数绑定在一起,生成一个新的可调用对象。这个新的可调用对象在调用时,会调用原始的可调用对象,并传递给它预先绑定的参数(如果有的话),以及调用新可调用对象时提供的任何额外参数。
2.5 识别线程
2种方法
- 第一种,可以通过调用std::thread对象的成员函数get_id()来直接获取。如果std::thread对象没有与任何执行线程相关联,get_id()将返回std::thread::type默认构造值,这个值表示“没有线程”。
- 第二种,当前线程中调用std::this_thread::get_id()(这个函数定义在头文件中)也可以获得线程标识。
第3章 线程间共享数据
3.2.1 C++中使用互斥量
C++中通过实例化std::mutex创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。实践中更推荐使用RAII语法的模板类std::lock_guard。
加了锁之后,还需要注意:被保护量不会通过指针或引用方式传递到外部去。
避免死锁的进阶指导
死锁定义:它指的是两个或多个线程在执行过程中,因争夺资源而造成的一种僵局(互相等待)。
避免死锁的方法:
- 避免使用多个锁
- 保持锁的顺序一致
- 避免嵌套锁
- 使用超时机制
- 使用标准库中的工具
C++标准库提供了一些工具来帮助避免死锁,如std::lock和std::scoped_lock。
std::lock可以一次性为多个互斥量上锁,并且内部使用死锁避免算法。
std::lock_guard RAII(Resource Acquisition Is Initialization)原则,这意味着资源的获取(在这里是互斥量的加锁)是在对象的构造函数中完成的,而资源的释放(在这里是互斥量的解锁)则是在对象的析构函数中完成的。
std::scoped_lock是一种RAII(Resource Acquisition Is Initialization)风格的锁管理器,它可以在构造时自动上锁,并在析构时自动解锁,从而简化了锁的管理。对多个锁进行操作,C++ 17引入。
std::unique_lock提供了比 std::lock_guard 更加灵活的互斥量封装。std::unique_lock 提供了更多的控制,包括延迟加锁、尝试加锁、定时加锁以及手动解锁和重新加锁的能力。 - 持有锁的时间尽可能少,减少死锁概率。
保护共享数据的替代设施
保护共享数据的初始化过程
双重检查锁存在的问题:
由于C++11之前的内存模型并没有提供足够的保证来防止指令重排序,因此在多线程环境中使用双重检查锁模式可能会导致未定义行为,比如访问未完全初始化的对象。
std::call_once 是 C++11 引入的一个函数,它属于 头文件。这个函数的主要用途是确保某个函数或可调用对象只被执行一次,即使它被多次调用。这对于初始化全局变量或执行只需要执行一次的昂贵操作特别有用。
std::call_once 的使用通常与 std::once_flag 类型的标志一起,这个标志用来指示函数是否已经被调用过。如果函数已经被调用,那么后续的调用将不会执行任何操作。
在C++11标准中,静态局部变量即便在多线程中,也只被初始化一次。
保护很少更新的数据结构(读多写少)
boost::shared_mutex 是 Boost 库中的一个同步原语,它允许多个线程以共享模式(shared mode)同时读取数据,但写入数据时需要独占访问。
使用 boost::shared_lockboost::shared_mutex 来获取共享锁。
使用 std::unique_lockboost::shared_mutex 来获取独占锁。
本质和读写锁类似。
嵌套锁
std::recursive_mutex,支持一个线程尝试锁多次。适用于获取锁的时候用到了其他函数,其他函数也访问这个锁。
更好的替代方法是:考虑是否可以重新设计函数逻辑,避免递归调用,从而无需使用递归互斥锁。
第4章 同步并发操作
线程会等待一个特定事件的发生,或者等待某一条件达成(为true)。像这种情况就需要在线程中进行同步,C++标准库提供了一些工具可用于同步操作,形式上表现为条件变量(condition variables)和期望(futures)。
4.1 等待一个事件或其他条件
条件变量:condition_variable
是利用线程间共享的变量进行同步的一种机制。在多线程程序中,条件变量常用于实现“等待–>唤醒”逻辑,用于维护一个条件(注意区分条件变量与条件本身),线程可以使用条件变量来等待某个条件为真。
常与锁结合。
条件变量的基本原理包括两个主要动作:
等待:当某个条件不满足时,一个线程会将自己加入等待队列,并释放持有的互斥锁(Mutex),进入睡眠状态等待条件成立。wait函数,需要带条件,可能虚假唤醒。
唤醒:当条件满足时,另一个线程会通知(signal或broadcast)等待在条件变量上的线程,唤醒它们重新检查条件。被唤醒的线程会重新尝试获取互斥锁,并在获取锁后继续执行。notify_one()与notify_all()。
4.2 使用期望等待一次性事件
std::future
获得一个 std::future 对象,这个对象将在未来某个时间点持有异步操作的结果。
关键函数:
- get():这个函数阻塞当前线程,直到异步操作完成,并返回操作的结果。如果异步操作抛出了异常,get()
函数将重新抛出该异常。注意,get() 只能被调用一次,因为一旦结果被取出,std::future 对象就不再持有任何结果了。 - wait():这个函数也会阻塞当前线程,但它只是等待异步操作完成,而不返回结果。如果只是想等待异步操作完成而不关心结果,可以使用这个函数。
- wait_for() 和wait_until():这两个函数提供了更灵活的等待机制。它们允许你指定一个时间段或时间点,然后在这个时间段内等待异步操作完成。如果操作在这段时间内完成了,函数将返回std::future_status::ready;如果操作没有完成,函数将返回 std::future_status::timeout或 std::future_status::deferred(对于 std::async 启动的异步任务,后者几乎不会出现)。
- valid():这个函数检查 std::future 对象是否还持有有效的异步操作结果。一旦 get()被调用或异步操作被取消,valid() 将返回 false。
std::async
是 C++11 标准库中的一个函数模板,它提供了一种方便的方式来启动一个异步任务。当你调用 std::async 时,你可以指定要执行的函数(或可调用对象)、传递给该函数的参数,以及一个启动策略(可选)。std::async 返回一个 std::future 对象,这个对象将在未来某个时间点持有异步操作的结果。
启动策略:
- std::launch::async:指示 std::async 应该异步地执行函数,即在新线程或线程池中执行。如果系统无法立即启动新线程,则行为是未定义的(尽管在实际的 C++ 实现中,它通常会阻塞直到能够启动新线程)。
- std::launch::deferred:指示 std::async 应该延迟执行函数,直到调用返回的 std::future 对象的 wait() 或 get() 方法。此时,函数将在调用这些方法的线程中同步执行。
如果省略启动策略,则 std::async 可能会选择 std::launch::async 或 std::launch::deferred,或者在某些情况下甚至可能使用混合策略。然而,这种混合模式的使用是不确定的,因此最好明确指定你想要的启动策略。
std::packaged_task<>
是 C++11 标准库中提供的一个模板类,它封装了一个可调用对象(如函数、lambda 表达式、绑定表达式等),使得这个可调用对象可以异步执行。可以通过调用 std::packaged_task<> 的 operator() 来异步地执行封装的可调用对象(比std::async更灵活)。
使用std::promises
是 C++11 标准库中提供的一个类模板,它用于在异步编程中设置值或异常,以便与 std::future 对象共享这些值或异常。std::promise 和 std::future 一起工作,以支持跨线程的值传递和异常传播。
主要函数:
- get_future(): 返回与 std::promise 对象关联的 std::future 对象。这个函数只能被调用一次。
- set_value(T value): 设置 std::promise 对象的值。这个函数只能被调用一次,并且只能在std::promise 对象被销毁之前调用。
- set_exception(std::exception_ptr p): 设置std::promise 对象的异常。这个函数也只能被调用一次,并且只能在 std::promise 对象被销毁之前调用。
多个线程的等待同一个事件
使用std::shared_future::wait,std::shared_future 与 std::future 类似,但主要区别在于 std::shared_future 可以被多个线程或对象共享,而 std::future 一旦被移动或拷贝后,原始对象将不再持有任何结果,变成空状态。
第6章 基于锁的并发数据结构设计
4.2 使用期望等待一次性事件
相关文章:

《c++并发编程实战》 笔记
《c并发编程实战》 笔记 1、你好,C的并发世界为什么要使用并发 第2章 线程管理2.1.1 启动线程2.2 向线程函数传递参数2.5 识别线程 第3章 线程间共享数据3.2.1 C中使用互斥量避免死锁的进阶指导保护共享数据的替代设施 第4章 同步并发操作4.1 等待一个事件或其他条件…...

57qi5rW35LqRZUhS pc.mob SQL注入漏洞复现
0x01 产品简介 57qi5rW35LqRZUhS是大中型企业广泛采用人力资源管理系统。某云是国内顶尖的HR软件供应商,是新一代eHR系统的领导者。 0x02 漏洞概述 57qi5rW35LqRZUhS pc.mob 接口存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例…...

微信小程序--27(自定义组件4)
一、父子组件之间通信的3种方式 1、属性绑定 用于父组件向子组件的只当属性设置数据,但只能设置JSON兼容的数据 2、事件绑定 用于子组件向父组件传递数据,可以传递任意数据 3、获取组件实例 父组件还可以通过this.select Component()获取子组件的实…...

Linux | Linux进程万字全解:内核原理、进程状态转换、优先级调度策略与环境变量
目录 1、从计算机组成原理到冯诺依曼架构 计算机系统的组成 冯诺依曼体系 思考:为什么计算机不能直接设计为 输入设备-CPU运算-输出设备 的结构? 2、操作系统(Operator System) 概念 设计OS的目的 描述和组织被管理对象 3、进程 基本概念 进程id和父进程…...

VBA技术资料MF184:图片导入Word添加说明文字设置格式
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套,分为初级、中级、高级三大部分,教程是对VBA的系统讲解&#…...

在函数设计中应用单一职责原则:函数分解与职责分离
在函数设计中应用单一职责原则:函数分解与职责分离 引言 单一职责原则(Single Responsibility Principle, SRP)是面向对象设计原则中的核心原则之一,强调一个类或函数应该只有一个责任或理由去改变。在函数设计中,应…...

多线程锁机制面试
目录 乐观锁的底层原理 ReentrantLock的实现原理 读写锁 ReentrantReadWriteLock synchronized 底层原理 Lock和synchronized的区别 乐观锁的底层原理 版本号机制 在数据库表中添加一个版本号字段(如 version),每次更新数据时都会将版本号…...

《SQL 中计算地理坐标两点间距离的魔法》
在当今数字化的世界中,地理数据的处理和分析变得越来越重要。当我们面对一个包含地理坐标数据的表时,经常会遇到需要计算两点之间距离的需求。无论是在物流配送路线规划、地理信息系统应用,还是在基于位置的服务开发中,准确计算两…...

微服务可用性设计
一、隔离 对系统或资源进行分割,实现当系统发生故障时能限定传播范围和影响范围。进一步的,通过隔离能够降低系统之间得耦合度,使得系统更容易维护和扩展。某些业务场景下合理使用隔离技巧也能提高整个业务的性能。我理解隔离本质就是一种解…...

【扒代码】dave readme文档翻译
jerpelhan/DAVE (github.com) 摘要 低样本计数器估算选定类别对象的数量,即使在图像中只有少量或没有标注样本的情况下。目前最先进的技术通过对象位置密度图的总和来估算总数量,但这种方法无法提供单个对象的位置和大小,这对于许多应用来说…...

c语言---文件
这一节我准备分三个部分来带领大家了解文件 ——一、有关文件的基础知识 ————二、文件的简单操作 ————————三、文件结束的判定 ————————————四、文件缓冲区 一、文件的基础知识: 首先在了解文件之前,我们需要了解C/C程序内存…...

Windows系统下Go安装与使用
step1: 下载go语言SDK 下载地址:https://go.dev/dl/ 下载后选择合适位置安装即可,我选择D盘 在安装完成后,可以通过go env 命令检测是否安装成功。在“命令提示符”界面输入“go env”命令,如果显示如下类似结果则说明…...

day24-测试之接口测试基础
目录 一、接口的定义 二、接口的优点 三、API接口 四、接口测试流程 五、网络基础概念 六、HTTP和RURL 七、get和post请求 八、数据格式 九、状态码 十、restful风格 十一、接口工具 一、接口的定义 程序之间协作所要遵循的一套规范、标准 二、接口的优点 2.1.责任…...

TSN 交换机
TSN(Time-Sensitive Networking)交换机是一种支持时间敏感网络协议的网络交换设备,用于在以太网网络中实现低延迟、高确定性的数据传输。TSN 是一组 IEEE 802 标准的集合,旨在通过标准化的方式,将传统的以太网扩展到需…...

针对thinkphp站点的漏洞挖掘和经验分享
0x1 前言 浅谈 目前在学习和研究thinkphp相关漏洞的打法,然后最近对于thinkphp资产的收集方面有了一个简单的认识,然后写一篇新手看的thinkphp相关的漏洞收集和挖掘的文章来分享下。然后后面是给师傅们分享下后台文件上传,然后直接打一个ge…...

MySQL数据库入门,pycharm连接数据库—详细讲解
一.安装MySQL 1.常用MySQL5.7,首先安装MySQL, (一) (二) (三) (四) (五) 2.配置环境变量 打开MySQL安装路径,在其中找到…...

.bat文件快速运行vue项目
如何使用bat文件快速运行vue项目? 新建个文件,改名为serve.bat。 在文件中写入以下内容: # cd 项目路径 cd D:\projects\xxx npm run serve pausecd 项目所在的路径 npm run dev/serve ,取决于项目的启动方法,打…...

数据结构(邓俊辉)学习笔记】优先级队列 07——堆排序
1.算法 作为完全二叉堆的一个应用,这节来介绍堆排序算法。 是的,谈到优先级队列,我们很自然地就会联想到排序。因为就其功能而言,包括完全二叉堆在内的任何一种优先级队列都天生地具有选取功能,也就是选取其中的最大…...

npm install pnpm -g 报错的解决方法
npm install pnpm -g 报错的解决方法 npm error code ETIMEDOUT npm error errno ETIMEDOUT npm error network request to https://registry.npmjs.org/pnpm failed, reason: npm error network This is a problem related to network connectivity. npm error network In mo…...

集师知识付费小程序开发
智慧生活,从选择一款优质知识付费小程序起航 在这个信息爆炸的时代,知识成为了最宝贵的财富。我们渴望不断学习,提升自我,追求更高品质的生活。而一款优质的知识付费小程序,就如同照亮前行道路的明灯。 它是知识的宝库…...

前端开发提效工具——用户自定义代码片段
做开发总是会有大量的代码要写,但是有时候某些代码是非常基础但是很多,我们就可以把这一部分整合起来,使用一个很简短的关键字来快速唤出。 如何新建这样的代码段? 1.在VSCode当中找到Snippets,然后点击 2.之后会弹出…...

docker容器安全加固参考建议——筑梦之路
这里主要是rootless的方案。 在以 root 用户身份运行 Docker 会带来一些潜在的危害和安全风险,这些风险包括: 容器逃逸:如果一个容器以 root 权限运行,并且它包含了漏洞或者被攻击者滥用,那么攻击者可能会成功逃出容器…...

基于 Appium 的 App 爬取实战
除了运行 Appium 的基本条件外,还要一个日志输出库 安装: pip install loguru 思路分析 首先我们观察一下整个 app5 的交互流程,其首页分条显示了电影数据, 每个电影条目都包括封面,标题, 类别和评分 4…...

nvm与node安装
参考: 一文搞定NVM安装所有问题NVM UI解决nodejs下载慢问题 node_mirror: http://npmmirror.com/mirrors/node/ npm_mirror: http://registry.npmmirror.com/mirrors/npm/解决nvm list available报错问题 Could not retrieve https://npm.taobao.org/mirrors/node/…...

【电子通识】什么是MSL湿敏等级
潮敏失效是塑料封装表贴器件在高温焊接工艺中表现出来的特殊的失效现象。 造成此类问题的原因是器件内部的潮气膨胀后使得器件发生损坏。 MSL是“Moisture Sensitivity Level(湿气敏感性等级)”的缩写,针对需进行回流焊的产品设定了MSL基准。…...

【ARM 芯片 安全与攻击 5.4 -- Meltdown 攻击与防御介绍】
文章目录 什么是 Meltdown 攻击?Meltdown 攻击的基本原理Meltdown 攻击代码示例Meltdown 攻击在芯片中的应用应用场景Meltdown 攻击与瞬态攻击、测信道攻击的关系针对 Meltdown 攻击的防御硬件级防御Summary什么是 Meltdown 攻击? Meltdown 攻击是一种利用处理器乱序执行(o…...

Django 后端架构开发:分页器到中间件开发
🚀 Django 后端架构开发:分页器到中间件开发 🚀 🔹 应用样式:上下翻页 分页功能在处理大量数据时非常有用。通过上下翻页,我们可以让用户轻松浏览数据。以下是一个展示产品列表的分页示例: fr…...

亲测解决The client socket has failed to connect to
这个问题是因为深度学习的程序(服务)跟本地主机连接不上,解决方法是确认rank起始数为0。 报错原文 [W socket.cpp:663] [c10d] The client socket has failed to connect to [csdn-xiaohu]:12345 (errno: 22 - Invalid argument).解决方法 …...

Intel ACRN 安装WIN10 VM
上一篇帖子记录了ACRN运行rt linux,这篇帖子记录一下最近倒腾出来的WIN10。目前架构如下 ACRN可以把它理解为一个基于Linux类似软件的Type1 Hypervisor,基于Linux去做而不是baremetal是为了更方便去配置资源。 首先我们得有两台电脑,一台是开…...

贷齐乐案例
源码分析: <?php // 设置 HTTP 头部,指定内容类型为 text/html,字符集为 utf-8 header("Content-type: text/html; charsetutf-8"); // 引入数据库配置文件 require db.inc.php; // 定义函数 dhtmlspecialchars,用…...