c++ 学习笔记之多线程:线程锁,条件变量,唤醒指定线程
基于CAS线程加锁方式
CAS(Compare-And-Swap)和 mutex
都是用于实现线程安全的技术,但它们适用于不同的场景,具有不同的性能和复杂性。下面是对两者的区别和使用场景的详细解释:
CAS(Compare-And-Swap)
工作原理
CAS 是一种无锁(lock-free)的同步机制,通过原子操作来比较和交换变量的值。它的核心思想是:只有当变量的当前值等于预期值时,才将其更新为新值,否则重新尝试。这个操作通常在硬件级别支持,以确保其原子性。
优点
- 高性能:由于没有锁的开销,CAS 通常比互斥锁更快,尤其是在争用较少的情况下。
- 无锁编程:CAS 允许多线程环境下的无锁编程,避免了死锁问题。
- 可扩展性:在高并发场景下,CAS 能提供更好的可扩展性,因为它减少了线程间的竞争。
缺点
- 复杂性:编写无锁代码更复杂,容易出错,特别是在处理复杂数据结构时。
- ABA问题:CAS 操作可能会受到 ABA 问题的影响,即一个值被修改成另一个值后又变回原值,导致 CAS 操作误认为值没有变化。
- 不适合长时间操作:如果需要进行长时间操作,CAS 可能不适用,因为在高争用情况下,CAS 可能会反复失败,导致性能下降。
使用场景
- 短小且频繁的操作:适用于操作简短且需要高频率执行的场景,如计数器、指针交换等。
- 高并发环境:适用于高并发但低争用的场景,如无锁队列、栈等数据结构。
mutex
(互斥锁)
工作原理
mutex
是一种基于锁的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。线程在进入临界区前必须获取锁,离开时释放锁。
优点
- 易于理解和使用:
mutex
的使用方式相对简单,易于理解和实现。 - 适用广泛:适用于需要保护临界区的各种场景,包括长时间的复杂操作。
- 解决复杂同步问题:对于复杂的共享资源访问,
mutex
提供了可靠的解决方案。
缺点
- 性能开销:获取和释放锁的操作有一定的性能开销,尤其是在高并发环境下。
- 死锁风险:如果使用不当,可能会导致死锁,即两个或多个线程互相等待对方释放锁。
- 上下文切换开销:在高争用情况下,线程可能频繁阻塞和唤醒,导致上下文切换开销。
使用场景
- 复杂操作和长时间操作:适用于需要长时间保护的临界区操作,特别是复杂的共享数据结构修改。
- 低到中等并发环境:适用于并发度不高的环境,或者即使在高并发环境中,也能有效管理临界区的访问。
- 简单同步需求:对于简单的同步需求,如单个资源的访问控制,
mutex
是一个可靠的选择。
总结
- CAS:适用于短小、频繁且简单的操作,特别是在高并发但低争用的场景中,能提供更好的性能和可扩展性,但编写无锁代码更复杂。
mutex
:适用于长时间、复杂的操作,以及需要可靠保护的临界区,易于理解和使用,但在高并发环境下性能可能不如无锁方案。
示例:
#include <iostream>
#include <thread>
#include <atomic>std::atomic<int> num(0); // 使用std::atomic定义一个原子整数void increment() {for (int i = 0; i < 1000; ++i) {int expected = num.load();while (!num.compare_exchange_weak(expected, expected + 1)) {// 如果CAS失败,expected会被更新为当前的值,继续尝试}}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value of num: " << num << std::endl;return 0;
}
条件变量:std::condition_variable
条件变量的工作原理
-
线程等待 (
wait
):- 当线程调用
wait
方法时,它会自动释放与条件变量相关联的互斥锁,并进入等待状态。 - 线程在等待期间会阻塞,直到被其他线程通知。
- 重要的是,
wait
方法会确保线程在被唤醒时会重新获得互斥锁,这样它可以安全地检查和更新条件。
- 当线程调用
-
线程通知 (
notify_all
或notify_one
):- 当某个线程改变了共享状态(即更新了条件)后,它会调用
notify_all
或notify_one
方法来通知其他线程。 notify_all
会唤醒所有在条件变量上等待的线程,而notify_one
只会唤醒一个线程(如果有多个线程等待)。
- 当某个线程改变了共享状态(即更新了条件)后,它会调用
来控制多个线程的执行顺序。以下是一个示例代码,实现了四个线程按照指定顺序(2 -> 1 -> 4 -> 3)循环执行:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <algorithm>std::mutex mtx;
std::condition_variable cv;
int current_thread = 2; // 初始线程编号
const std::vector<int> order = {2, 1, 4, 3}; // 执行顺序
const int num_iterations = 10; // 每个线程执行的循环次数void thread_function(int thread_id) {for (int i = 0; i < num_iterations; ++i) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [thread_id]() { return current_thread == thread_id; });// 执行线程的任务std::cout << "Thread " << thread_id << " is executing\n";// 更新当前线程编号auto it = std::find(order.begin(), order.end(), thread_id);if (it != order.end() && ++it != order.end()) {current_thread = *it;} else {current_thread = order[0];}// 唤醒所有等待线程cv.notify_all();}
}int main() {std::vector<std::thread> threads;// 创建四个线程for (int i = 1; i <= 4; ++i) {threads.emplace_back(thread_function, i);}// 等待所有线程完成for (auto& t : threads) {t.join();}return 0;
}
notify_all
和 notify_one
的区别
-
notify_all
:- 功能:唤醒所有在条件变量上等待的线程。
- 使用场景:当条件变化时,你希望所有等待的线程都能被唤醒并检查条件。例如,当一个共享资源的状态发生变化,并且所有等待的线程都需要重新检查状态时,使用
notify_all
是合适的。 - 缺点:如果有大量线程等待,并且每次通知都会唤醒所有线程,可能会导致性能问题,因为所有线程都会被唤醒并重新竞争锁。
-
notify_one
:- 功能:唤醒一个在条件变量上等待的线程。如果有多个线程等待,哪个线程被唤醒是不确定的。
- 使用场景:当条件变化时,只需要唤醒一个线程进行处理,其他线程可以继续等待。例如,当有线程处理某个任务时,你可能只需要唤醒一个线程来处理它,并且其他线程可以继续等待。
- 优点:比
notify_all
更高效,特别是在只需唤醒一个线程的情况下,减少了不必要的线程唤醒和锁竞争。
多线程编程中,怎么唤醒指定的线程
在多线程编程中,标准的 C++ 条件变量 (std::condition_variable
) 并不提供直接指定唤醒哪个特定线程的功能。std::condition_variable
提供的 notify_one()
和 notify_all()
方法仅仅是唤醒一个或所有等待线程,并不支持精确指定具体的线程。要实现更细粒度的线程控制,你需要采用其他方法。以下是一些常见的方法来间接实现唤醒特定线程的需求:
1. 线程标识符与条件变量
你可以使用标识符或某种标志来控制哪个线程应该继续执行。这种方法不直接唤醒特定线程,但通过条件变量和额外的状态管理来实现间接的控制。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>std::mutex mtx;
std::condition_variable cv;
bool ready[4] = {false, false, false, false}; // 每个线程的准备状态void worker(int id) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [id]{ return ready[id]; }); // 等待特定条件std::cout << "Worker " << id << " is processing\n";
}void set_ready(int id) {std::lock_guard<std::mutex> lock(mtx);ready[id] = true;cv.notify_all(); // 或 notify_one(),具体取决于你的需求
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 4; ++i) {threads.emplace_back(worker, i);}std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间// 唤醒特定的线程set_ready(2); // 只唤醒 id 为 2 的线程for (auto& t : threads) {t.join();}return 0;
}
2. 使用 std::promise
和 std::future
std::promise
和 std::future
提供了另一种方式来管理线程间的同步,并可以用于将结果传递给特定线程。
#include <iostream>
#include <thread>
#include <future>
#include <vector>std::vector<std::promise<void>> promises(4);
std::vector<std::future<void>> futures;void worker(int id) {futures[id].wait(); // 等待对应的 promise 被设置std::cout << "Worker " << id << " is processing\n";
}int main() {for (int i = 0; i < 4; ++i) {futures.push_back(promises[i].get_future());std::thread(worker, i).detach();}std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间// 唤醒特定的线程promises[2].set_value(); // 唤醒 id 为 2 的线程std::this_thread::sleep_for(std::chrono::seconds(1)); // 给线程时间完成任务return 0;
}
相关文章:
c++ 学习笔记之多线程:线程锁,条件变量,唤醒指定线程
基于CAS线程加锁方式 CAS(Compare-And-Swap)和 mutex 都是用于实现线程安全的技术,但它们适用于不同的场景,具有不同的性能和复杂性。下面是对两者的区别和使用场景的详细解释: CAS(Compare-And-Swap&…...

《0基础》学习Python——第二十三讲__网络爬虫/<6>爬取哔哩哔哩视频
一、在B站上爬取一段视频(B站视频有音频和视频两个部分) 1、获取URL 注意:很多平台都有反爬取的机制,B站也不例外 首先按下F12找到第一条复制URL 2、UA伪装,下列图片中(注意代码书写格式) 3、Co…...

第13周 简历职位功能开发与Zookeeper实战
第13周 简历职位功能开发与Zookeeper实战 本章概述1. Mysql8窗口函数over使用1.1 演示表结构与数据1.2 案例1:获取男女总分数1.3 案例2****************************************************************************************本章概述 1. Mysql8窗口函数over使用 参考案例…...

什么是大型语言模型 (LLM)
本章探讨下,人工智能如何彻底改变我们理解和与语言互动的方式 大型语言模型 (LLM) 代表了人工智能的突破,它采用具有广泛参数的神经网络技术进行高级语言处理。 本文探讨了 LLM 的演变、架构、应用和挑战,重点关注其在自然语言处理 (NLP) 领…...

【人工智能】AI时代:探索个人潜能的新视角
文章目录 🍊Al时代的个人发展1 AI的高速发展意味着什么1.1 生产力大幅提升1.2 生产关系的改变1.3 产品范式1.4 产业革命1.5 Al的局限性1.5.1局限一:大模型的幻觉1.5.2 局限二:Token 2 个体如何应对这种改变?2.1 职场人2.2 K12家长2.3 大学生2.4 创业者 …...
pyaudio VAD通过声音音频值分贝大小检测没人说话自动停止录制
效果可能说话声音小可能不被监听到,需要更改QUIET_DB阈值,另外delay_time值是低于阈值多久就可以停止保存当前的语音 import pyaudio import waveimport sys import numpy as npdef record_auto(MIC_INDEX=1):开启麦克风录音,保存至temp/speech_record.wav音频文件音量超过…...

《后端程序猿 · @Value 注释说明》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...

【LeetCode】71.简化路径
1. 题目 2. 分析 3. 代码 我写了一版很复杂的代码: class Solution:def simplifyPath(self, path: str) -> str:operator [] # 操作符的栈dir_name [] # 文件名的栈idx 0cur_dir_name ""while(idx < len(path)):if path[idx] /:operator.ap…...
DockerCompose 安装环境
1. Redis version: 3 services:redis:image: redis:6.2.12container_name: redisports:- "6379:6379"environment:TZ: Asia/Shanghaivolumes:# 本地数据目录要先执行 chmod 777 /usr/local/docker/redis/data 赋予读写权限,否则将无法写入数据- /usr/loc…...

学习笔记之JAVA篇(0724)
p 方法 方法声明格式: [修饰符1 修饰符2 ...] 返回值类型 方法名(形式参数列表){ java语句;......; } 方法调用方式 普通方法对象.方法名(实参列表)静态方法类名.方法名(实参列表) 方法的详…...

【Android】广播机制
【Android】广播机制 前言 广播机制是Android中一种非常重要的通信机制,用于在应用程序之间或应用程序的不同组件之间传递信息。广播可以是系统广播,也可以是自定义广播。广播机制主要包括标准广播和有序广播两种类型。 简介 在Android中,…...
【.NET全栈】ASP.NET开发Web应用——ASP.NET数据绑定技术
文章目录 前言一、绑定技术基础1、单值绑定2、重复值绑定 二、数据源控件1、数据绑定的页面生存周期2、SqlDataSource3、使用参数过滤数据4、更新数据和并发处理5、编程执行SqlDataSource命令6、ObjectDataSource控件介绍7、创建业务对象类8、在ObiectDataSource中使用参数9、使…...

MySQL的账户管理
目录 1 密码策略 1.1 查看数据库当前密码策略: 1.2 查看密码设置策略 1.3 密码强度检查等级解释(validate_password.policy) 2 新建登录账户 3 账户授权 3.1 赋权原则 3.2 常见的用户权限 3.3 查看权限 3.4 赋权语法 4 实例 4.1 示例1&#x…...

FastGPT 源码调试配置
目录 一、添加 launch.json 文件 二、调试 本文简单介绍如何通过 vscode 对 FastGPT 进行调试。 这里假设已经安装 vsocde 和 FastGPT本地部署。 一、添加 launch.json 文件 vscode 打开 FastGPT 项目,点击 调试 -> 显示所有自动调试配置 -> 添加配置 -> Node.j…...
SQL Server数据迁移新纪元:数据库数据泵(Data Pump)使用指南
SQL Server数据迁移新纪元:数据库数据泵(Data Pump)使用指南 在数据管理的世界里,数据迁移是一个常见且复杂的过程。SQL Server提供了一个强大的工具——数据库数据泵(Data Pump),它可以帮助我…...
Android性能优化之OOM
OOM 什么是OOM?为什么会有OOM?APP的内存限制App的内存限制是多少? 为什么Android系统要设定App的内存限制?Android有GC自动回收资源,为什么还会OOM?容易发生OOM的场景及处理方案如何避免OOM? 什么是OOM&am…...

代码随想录算法训练营day7 | 454.四数相加II、383.赎金信、15.三数之和、18.四数之和
文章目录 454.四数相加II思路 383.赎金信思路 15.三数之和思路剪枝去重 18.四数之和思路剪枝去重复习:C中的类型转换方法 总结 今天是哈希表专题的第二天 废话不多说,直接上题目 454.四数相加II 建议:本题是 使用map 巧妙解决的问题&#x…...

Spark实时(三):Structured Streaming入门案例
文章目录 Structured Streaming入门案例 一、Scala代码如下 二、Java 代码如下 三、以上代码注意点如下 Structured Streaming入门案例 我们使用Structured Streaming来监控socket数据统计WordCount。这里我们使用Spark版本为3.4.3版本,首先在Maven pom文件中导…...

《Java初阶数据结构》----4.<线性表---Stack栈和Queue队列>
前言 大家好,我目前在学习java。之前也学了一段时间,但是没有发布博客。时间过的真的很快。我会利用好这个暑假,来复习之前学过的内容,并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区…...
Android SurfaceFlinger——关联EGL三要素(二十七)
通过前面的文章我们得到了 EGL 的三要素——Display、Surface 和 Context。其中,Display 是一个图形显示系统或者硬件屏幕,Surface 代表一个可以被渲染的图像缓冲区,Context 包含了 OpenGL ES 的状态信息和资源,它是执行 OpenGL 命令的环境。下一步就是调用 eglMakeCurrent…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...