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…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
