c++同步机制
总结
多线程访问共享数据时需要加锁
多线程数据竞争
假如有一个变量shared_variable被10个线程共享,每个线程在循环中对shared_variable进行 1000 次累加操作,我们期望最终值为10000。
#include <iostream>
#include <thread>
#include <vector>int shared_variable = 0; // 共享变量void thread_function() {for (int i = 0; i < 1000; ++i) {// 模拟长延迟,增加竞争机会std::this_thread::sleep_for(std::chrono::milliseconds(1));shared_variable++;}
}int main() {const int num_threads = 10;std::vector<std::thread> threads;// 创建多个线程for (int i = 0; i < num_threads; ++i) {threads.emplace_back(thread_function);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "Final value: " << shared_variable << std::endl; // 输出最终结果return 0;
}
实际运行我们发现,shared_variable值通常会小于 10000,并且每次运行结果不同。这是因为多个线程同时读取和修改shared_variable,导致其值被错误覆盖。++shared_variable是一个非原子操作,包含三步:
- 读取当前值。
- 加 1。
- 写回新值。
在多线程环境中,这三步之间可能会被其他线程打断,导致结果不一致。例如:
- 线程 A 读取到值 100,准备加 1。
- 线程 B 同时读取到值 100,也准备加 1。
- 线程 A 写回值 101。
- 线程 B 写回值 101(假设线程 B 未看到线程 A 的修改)。
期望的值应为 102,但实际上变为 101,导致错误。这就是在多线程编程中不使用锁导致数据竞争的典型问题。
使用锁同步
- List item
锁是一种同步机制,用于协调多个线程对共享资源的访问。它类似于现实生活中的锁,可以确保一次只有一线程能访问特定的资源,其他线程必须等待锁被释放才能继续执行。在多线程编程中,锁的主要作用是防止数据竞争和一致性问题,确保对共享数据的正确操作和访问顺序。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>std::mutex mtx; // 互斥锁
int shared_variable = 0; // 共享变量void thread_function() {for (int i = 0; i < 1000; ++i) { std::lock_guard<std::mutex> lock(mtx); // 获取锁shared_variable++; // 模拟长延迟,增加竞争机会std::this_thread::sleep_for(std::chrono::milliseconds(1));}
}int main() {const int num_threads = 10;std::vector<std::thread> threads;// 创建多个线程for (int i = 0; i < num_threads; ++i) {threads.emplace_back(thread_function);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "Final value: " << shared_variable << std::endl; // 输出最终结果return 0;
}
复合操作中的竞争条件问题
当shared_vector正在被一个线程修改时,另一个线程可能读取了无效的内存地址,导致内存越界和段错误。例如当多个线程同时执行复合操作时(例如,先检查 size() 再访问元素),仍然需要外部同步,因为复合操作本身不是原子的。
if (vec.size() > 0) { // 操作1:检查条件int value = vec.at(0); // 操作2:访问元素
}
具体的例子如下所示
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>std::vector<int> shared_vector; // 共享的 vector// 写线程,不断向 vector 中添加元素
void writer_thread()
{for (int i = 0; i < 1000000; ++i) {shared_vector.push_back(i); // 1. 线程A添加一个元素,vector不为空std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // 模拟延迟shared_vector.clear(); // 3. 清空 vector}
}// 操作线程,不断清空 vector 并访问最后一个元素
void reader_thread()
{while (true) {if (!shared_vector.empty()) { // 2. 线程B对vector判空std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // 模拟延迟int last_element = shared_vector.at(0); // 4. 尝试访问第一个元素,可能导致越界访问std::cout << "Last element: " << last_element << std::endl;}}
}int main()
{std::thread t1(writer_thread);std::thread t2(reader_thread);t1.join();t2.join();return 0;
}
这种情况还比较好分析,vector在添加元素超过某个size的时候会重新申请一块地址,如果另一个线程还在访问之前的地址,就会造成非法访问的问题
解决方案就是通过锁来保证复合操作的
std::mutex mtx;
std::vector<int> vec;if (mtx.lock()) {if (vec.size() > 0) { int value = vec.at(0);// 处理 value}mtx.unlock();
}
条件变量与线程同步
锁解决了数据竞争的问题,但是没有解决线程顺序执行的问题,例如我们想让3个线程顺序打出ABC,可以使用条件变量
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>// 全局变量
std::mutex mtx;
std::condition_variable cv;
char current_char = 'a'; // 当前应该打印的字符// 打印函数
void print_char(char target_char) {for (int i = 0; i < 5; ++i) { // 每个字符打印5次,可以根据需要调整std::unique_lock<std::mutex> lock(mtx);// 等待轮到自己打印cv.wait(lock, [target_char]() { return current_char == target_char; });// 打印字符std::cout << target_char << std::endl;// 更新当前字符到下一个if (target_char == 'a') {current_char = 'b';} else if (target_char == 'b') {current_char = 'c';} else {current_char = 'a';}// 通知其他线程cv.notify_all();}
}int main() {// 创建三个线程std::thread thread_a(print_char, 'a');std::thread thread_b(print_char, 'b');std::thread thread_c(print_char, 'c');// 等待线程完成thread_a.join();thread_b.join();thread_c.join();return 0;
}
条件变量为什么加锁
条件变量在使用时通常与锁(如 std::mutex)一起使用,主要原因如下:
1. 保证数据一致性和线程安全
条件变量用于线程之间的通信和同步,当线程等待某个条件时,必须确保该条件的状态是被保护起来的。如果没有锁保护,在线程等待条件时,其他线程可能会修改条件相关的数据,导致数据不一致和竞争条件。
例如,在生产者 - 消费者问题中,消费者线程等待队列非空的条件。如果消费者线程在检查队列是否为空的时候不使用锁保护,生产者线程可能会在消费者线程检查的中间过程插入数据,导致检查结果不准确。
void consumer() {while (true) {if (!queue.empty()) { // 没有锁保护,读取共享变量是不安全的int val = queue.front();queue.pop();std::cout << "Consumed: " << val << std::endl;} else {// 等待队列有数据cv.wait(); // 假设有条件变量 cv}}
}
问题:如果多个线程同时检查 queue.empty(),而没有锁保护,可能会导致竞争条件,因为 queue 的状态可能在检查和操作之间发生变化。
2. 确保条件判断的原子性
在使用条件变量时,通常需要进行以下三步操作:
- 锁定互斥锁。
- 检查条件是否成立。
- 如果条件不成立,等待条件变量。
通过互斥锁,可以确保这三步操作是原子的,即在多线程环境下,其他线程不能在步骤 2 和 3 之间修改条件相关的数据。
void consumer() {std::unique_lock<std::mutex> lock(mtx);while (queue.empty()) { // 使用锁保护,并使用 while 避免虚假唤醒cv.wait(lock); // 等待条件变量}int val = queue.front();queue.pop();std::cout << "Consumed: " << val << std::endl;
}
std::unique_lock<std::mutex> lock(mtx); 确保在检查条件和操作队列时,其他线程不能访问队列。while 循环确保即使由于虚假唤醒,线程在真正条件满足之前不会继续执行。
3. 避免虚假唤醒
条件变量的 wait 方法可能出现虚假唤醒(spurious wakeup),即线程在没有明确通知的情况下被唤醒。使用 std::unique_lock 和 while 循环可以避免这种情况。
错误示例(没有使用锁和 while 循环):
void consumer() {if (queue.empty()) {cv.wait(); // 可能出现虚假唤醒}// 虚假唤醒导致进入此处时队列可能仍然为空int val = queue.front();queue.pop();
}
正确示例:
void consumer() {std::unique_lock<std::mutex> lock(mtx);while (queue.empty()) { // 即使有虚假唤醒,也会重新检查条件cv.wait(lock);}int val = queue.front();queue.pop();std::cout << "Consumed: " << val << std::endl;
}
4. 确保线程间的可见性
条件变量和锁的组合使用可以确保线程之间的内存可见性。例如,当线程 A 修改了某个共享变量并通知条件变量,线程 B 通过锁保护可以保证看到线程 A 的修改。
void producer() {{std::lock_guard<std::mutex> lock(mtx);queue.push(1); // 修改共享数据}cv.notify_one(); // 通知消费者
}void consumer() {std::unique_lock<std::mutex> lock(mtx);while (queue.empty()) {cv.wait(lock);}int val = queue.front(); // 看到生产者线程修改后的数据queue.pop();
}
总结
条件变量和锁的组合使用是确保多线程环境下数据一致性和线程安全的关键。锁保护共享数据的访问,避免竞争条件;条件变量通过条件等待和通知机制,实现线程间的通信和同步。只有在锁的保护下,条件变量才能正确地工作,并保证线程之间的数据可见性。
相关文章:
c++同步机制
总结 多线程访问共享数据时需要加锁 多线程数据竞争 假如有一个变量shared_variable被10个线程共享,每个线程在循环中对shared_variable进行 1000 次累加操作,我们期望最终值为10000。 #include <iostream> #include <thread> #include …...
RuoYi框架介绍,以及如何基于Python使用RuoYi框架
若依框架(RuoYi)是一款基于Spring Boot和Vue.js的开源快速开发平台,广泛应用于企业级应用开发。它提供了丰富的功能模块和代码生成工具,帮助开发者快速搭建后台管理系统。 主要特点 前后端分离:前端采用Vue.js&#x…...
Go 语言环境安装
1.go官网下载安装包 All releases - The Go Programming Language 双击安装,一路下一步 2.安装完后查看版本 打开cmd 输入 以下 ,查看语言版本 go version 查看环境变量是否自动设置成功...
vector 面试点总结
ps:部分内容使用“AI”查询 一、入门 1、什么是vector 动态数组容器,支持自动扩容、随机访问和连续内存存储。 2、怎么创建-初始化vector std::vector<int> v; // 创建空vectorstd::vector<int> v {1, 2, 3}; // 直接初始化std::vec…...
Java 8 新特性
Java 8 引入了一系列重要的新特性,极大地增强了 Java 语言的功能,尤其是在 函数式编程、流处理、日期时间 API 和 默认方法 等方面。这些新特性不仅提升了代码的可读性和简洁性,还改善了并发处理的性能。以下是 Java 8 主要新特性的详细说明。…...
知识库技术选型:主流Embedding模型特性对比
知识库技术选型:主流Embedding模型特性对比 1. 知识库与大模型结合的背景 知识库是存储和管理结构化知识的系统,广泛应用于问答系统、推荐系统和搜索引擎等领域。随着大语言模型(LLM)的发展,知识库与大模型的结合成为…...
CAN总线通信协议学习2——数据链路层之帧格式
1 帧格式 帧格式可理解为定义了传输的数据(叫报文)应该“长什么样”来传输,也为后续设定一些规则如错误检查机制提供了思路。 首先,帧格式可分为以下5种类型: PS:CAN总线任意一个设备可当收也可当发&#…...
基于ArcGIS Pro、Python、USLE、INVEST模型等多技术融合的生态系统服务构建生态安全格局高阶应用
文字目录 前言第一章、生态安全评价理论及方法介绍一、生态安全评价简介二、生态服务能力简介三、生态安全格局构建研究方法简介 第二章、平台基础一、ArcGIS Pro介绍二、Python环境配置 第三章、数据获取与清洗一、数据获取:二、数据预处理(ArcGIS Pro及…...
神经网络在电力电子与电机控制中的应用
神经网络(Neural Networks)简介 神经网络是一种受生物神经元启发的机器学习模型,能够通过大量数据学习输入与输出之间的非线性映射关系。其核心结构包括: 输入层:接收外部数据(如传感器信号、控制指令&…...
llama-factory || AutoDL平台
报错如下: rootautodl-container-d83e478b47-3def8c49:~/LLaMA-Factory# llamafactory-cli webui * Running on local URL: http://0.0.0.0:7860Could not create share link. Missing file: /root/miniconda3/lib/python3.10/site-packages/gradio/frpc_linux_am…...
数学建模:MATLAB极限学习机解决回归问题
一、简述 极限学习机是一种用于训练单隐层前馈神经网络的算法,由输入层、隐藏层、输出层组成。 基本原理: 输入层接受传入的样本数据。 在训练过程中随机生成从输入层到隐藏层的所有连接权重以及每个隐藏层神经元的偏置值,这些参数在整个…...
力扣785. 判断二分图
力扣785. 判断二分图 题目 题目解析及思路 题目要求将所有节点分成两部分,每条边的两个端点都必须在不同集合中 二分图:BFS/DFS/并查集 因为图不一定联通,所以枚举所有点都做bfs(如果没联通的话) 代码 class Solution { public:bool is…...
【硬件工程师成长】之是否需要组合电容进行滤波的考虑
在电子电路设计中,判断是否需要使用组合电容进行滤波,需综合考虑以下因素: 1. 噪声频谱分析 高频与低频噪声共存:若电源或信号中同时存在低频(如工频纹波)和高频噪声(如开关电源的开关噪声、数字…...
Pythonweb开发框架—Flask工程创建和@app.route使用详解
1.创建工程 如果pycharm是专业版,直接NewProject—>Flask 填写工程name和location后,点击右下角【create】,就会新建一个flask工程,工程里默认会建好一个templates文件夹、static文件夹、一个app.py文件 templates࿱…...
005 公网访问 docker rocketmq
文章目录 创建自定义网络创建NameServer容器创建Broker容器正式开始启动 Nameserver 容器启动 Broker 容器并关联 Nameserverdocker exec -it rmqbroker vi /etc/rocketmq/broker.conf检查 namesrv 解析检查 Broker 注册状态Nameserver 日志Broker 日志检查容器日志手动指定 Br…...
C++11中的右值引用和完美转发
C11中的右值引用和完美转发 右值引用 右值引用是 C11 引入的一种新的引用类型,用 && 表示。它主要用于区分左值和右值,并且可以实现移动语义,避免不必要的深拷贝,提高程序的性能。左值通常是可以取地址的表达式…...
txt 转 json 使用python语言
需求: 把如下的txt文档转成json输出 代码 import jsondef txt_to_json(input_file, output_file):data_list []with open(input_file, r, encodingutf-8) as f:for line in f:# 分割数据并去除换行符parts line.strip().split(,)print(f"{parts}")print(type(par…...
Android Logcat 高效调试指南
工具概览 Logcat 是 Android SDK 提供的命令行日志工具,支持灵活过滤、格式定制和实时监控,官方文档详见 Android Developer。 基础用法 命令格式 [adb] logcat [<option>] ... [<filter-spec>] ... 执行方式 直接调用(通过ADB守…...
【Linux】从入门到精通:Make与Makefile完全指南
欢迎来到 CILMY23 的博客 🏆本篇主题为:从入门到精通:Make与Makefile完全指南 🏆个人主页:CILMY23-CSDN博客 🏆系列专栏:C | C语言 | Linux | Python | 数据结构和算法 | 算法专题 …...
leetcode0014 最长公共前缀 -easy
1 题目:最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 “”。 示例 1: 输入:strs [“flower”,“flow”,“flight”] 输出:“fl” 示例 2: 输入&a…...
SwiftData中的级联删除关系
在开发iOS应用程序时,数据模型的设计和管理至关重要。SwiftData是Apple在WWDC 2023上推出的新框架,旨在简化数据模型的创建和操作。今天,我们来探讨一下如何在SwiftData中实现级联删除关系。 什么是级联删除? 级联删除是一种数据库关系模型中的操作规则,当一个主记录被删…...
hello-uniapp网络状态监听:提升应用健壮性的终极指南
hello-uniapp网络状态监听:提升应用健壮性的终极指南 【免费下载链接】hello-uniapp uni-app框架演示示例 项目地址: https://gitcode.com/gh_mirrors/he/hello-uniapp 在移动应用开发中,网络状态的稳定性直接影响用户体验和应用可靠性。hello-un…...
Zynq-7000 + RT-Thread + lwIP 实时网络性能调优实战
1. 为什么选择Zynq-7000 RT-Thread lwIP组合 在嵌入式网络应用中,实时性和确定性往往是首要考虑因素。我曾在多个工业控制项目中遇到这样的场景:系统需要同时处理高速UDP数据流和稳定的TCP控制指令,传统的嵌入式Linux方案虽然功能全面&…...
奇安信浏览器HEVC硬件解码优化指南:基于JM9显卡的实战配置
1. 为什么需要HEVC硬件解码优化 最近在折腾4K视频播放时,发现电脑风扇狂转,CPU占用直接飙到90%以上。查了下才发现是浏览器软解HEVC视频导致的,这种场景下显卡却在旁边"看戏"。后来发现奇安信浏览器搭配JM9显卡的硬件解码方案&…...
OpenClaw学习助手:Qwen3.5-9B驱动的知识整理与习题生成
OpenClaw学习助手:Qwen3.5-9B驱动的知识整理与习题生成 1. 为什么需要AI学习助手? 去年备考PMP认证时,我每天要处理上百页PDF讲义。最痛苦的不是阅读,而是如何把关键知识点转化成可记忆的卡片和练习题。手动整理不仅耗时&#x…...
好写作AI“文献综述智囊团”:开启学术探索新航道
在学术研究的广袤天地中,文献综述宛如一座灯塔,为研究者照亮前行的道路,它不仅是对前人研究成果的全面梳理与总结,更是为后续研究搭建起坚实的理论基石。然而,撰写一份高质量的文献综述并非易事,海量文献的…...
深入解析Cache机制:从原理到性能优化实战
1. 从理论到实战:Cache概念的职场觉醒第一次真正理解Cache的重要性,是在我接手硬件性能监控项目的那一刻。当时领导让我用perf工具监控处理器性能,输入perf list后满屏的cache-misses、cache-loads指标让我彻底懵了——这些在大学《计算机组成…...
基于MATLAB平台PCA的人脸识别:开启识别新征程
基于MATLAB平台PCA的人脸识别,程序已调通,可将自己的数据替换进行识别。 得到识别准确率结果。最近在研究人脸识别技术,基于MATLAB平台利用PCA(主成分分析)实现了一个人脸识别程序,现在跟大家分享分享。 PC…...
国产SeekWave 双频WIFI6+BT5.4 VS6621SR80基于RK3588平台成功替换RTL8822模组 硬件兼容 速率可达600Mbps
RK3588是瑞芯微(Rockchip)推出的旗舰级SoC芯片,采用8nm工艺,集成四核Cortex-A76和四核Cortex-A55 CPU、ARM Mali-G610 MP4 GPU、6 TOPS NPU,支持8K视频编解码。12CPU:八核ARM架构ÿ…...
AI 模型推理延迟与吞吐率的权衡
AI模型推理延迟与吞吐率的权衡:优化策略与实践 在AI应用场景中,模型推理的延迟(Latency)和吞吐率(Throughput)是衡量系统性能的两大核心指标。延迟指单次请求的响应时间,直接影响用户体验&…...
