当前位置: 首页 > news >正文

C++多线程数据错乱

C++多线程数据错乱(也称为线程安全问题数据竞争)主要是由于多个线程在没有正确同步的情况下,并发访问和修改共享数据导致的。其主要原因包括以下几个方面:

、线程交替执行导致的非原子操作

线程在执行时,可能会在中途被挂起,然后另一个线程修改了同一个数据,导致数据不一致。

如下代码,两个线程分别对sum进行相加操作

long sum= 0L;void fun(size_t num)
{for (size_t i = 0; i < num; ++i){sum++;}
}int main()
{cout << "Before joining,sum = " << sum<< std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;system("pause");return 0;
}

该程序结果为任意值,并不是我们期望的20000000。原因如下:

  • 复合操作的分解风险
    自增(sum++)、修改对象属性等看似单行的操作,实际由多个汇编指令构成(读取→计算→写入)。当多线程交替执行这些步骤时,可能出现部分操作结果被覆盖,导致最终数据偏差。例如两个线程同时执行sum++可能导致总增量只有1而非2。

  • 无同步机制的并发写入
    多个线程直接修改同一变量时(如共享状态标志),若未通过锁或原子操作强制串行化,后写入的数据可能覆盖前一次修改,造成逻辑错误

  • 线程A读取 sum=0,计算 sum=1,但未写回
  • 线程B读取 sum=0,计算 count=1,然后写回
  • 线程A继续写回 sum=1
  • 最终 sum 只加了1,而不是2

二、指令重排序引发的时序问

2.1 编译器优化与CPU乱序执行
编译器为提高性能可能调整代码顺序,而现代CPU也会对指令进行动态重排。若共享变量的访问顺序影响业务逻辑(如先更新数据再标记完成),重排序会导致其他线程读取到中间状态。

// 示例:看似顺序执行的代码可能被重排序
std::unique_lock<std::mutex> lock(mu_tex);
account_list[from].subMoney(money);
++times;  // 可能被重排到addMoney前执行
account_list[to].addMoney(money);

2.2 内存屏障缺失
x86架构虽保证存储顺序一致性,但在弱内存模型架构(如ARM)中,缺少内存屏障可能导致写入延迟对其他线程可见,产生“过期读取”现象 

三、内存可见性与缓存一致性

  1. 线程私有缓存未同步
    各线程可能将共享变量缓存在CPU核心私有缓存中,修改后未及时刷新到主内存,导致其他线程读取旧值。MESI协议虽解决硬件级缓存一致性问题,但编程时仍需显式同步。

  2. volatile关键字的局限性
    仅用volatile修饰变量能禁止编译器优化重排,但无法保证多线程操作的原子性,仍需配合锁或原子类型实现完整同步。

下图是数据在计算机中的具体位置,可以看到,数据在CPU,高速缓存(在CPU内部),内存,DMA,GPU中都有副本,我们的程序要保证这些副本的值都一致

四、同步机制使用不当

  1. 锁粒度不合理
    粗粒度锁(如全局锁)会降低并发性能,细粒度锁(如分段锁)若设计不当则可能遗漏保护关键区域,两者均可能间接引发数据竞争36。

  2. 死锁与锁争用
    嵌套锁使用错误会导致线程永久阻塞,而高并发下锁竞争会加剧线程切换开销,两者都会影响系统可靠性。例如:

// 错误示例:两个线程按不同顺序获取锁导致死锁
void thread1() {lock(mutexA);lock(mutexB); // 若thread2已锁B,则阻塞
}void thread2() {lock(mutexB);lock(mutexA); // 若thread1已锁A,则阻塞
}

五、解决方案的工程实践

  • 原子类型‌:使用std::atomic确保单变量操作的原子性
  • 内存序控制‌:通过memory_order参数精细控制内存可见性
  • 无锁数据结构‌:基于CAS(Compare-And-Swap)实现高性能并发结构
  • 事务内存‌:C++ STM(Software Transactional Memory, STM)提供原子代码块声明
std::atomic<int> testValue(0);void fun(size_t num)
{for (size_t i = 0; i < num; ++i)testValue.fetch_add(1, std::memory_order_relaxed); // 使用memory_order_relaxed保证性能
}int main()
{cout << "Before joining,sum = " << testValue << std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining,sum = " << testValue << std::endl;system("pause");return 0;
}

总结

  • 多线程数据错乱的主要原因
    1. 非原子操作导致的并发修改问题
    2. 多个线程同时修改共享变量
    3. 内存可见性问题(缓存不同步)
    4. 指令重排序导致的执行顺序异常
  • 解决方案
    • 使用 lockvolatile
    • 使用 atomic 变量
    • 使用线程安全的集合

参考:

https://zhuanlan.zhihu.com/p/20055436026

多线程数据错乱_c++ 多线程并发访问导致的数据不一致性。-CSDN博客

【面试】解释两个线程为什么不能及时同步_多线程中网口线程回复数据时长不固定是怎么回事-CSDN博客 

相关文章:

  • 常见的请求头(Request Header)参数
  • SpringMVC-拦截器
  • 虚幻引擎5-Unreal Engine笔记之`GameMode`、`关卡(Level)` 和 `关卡蓝图(Level Blueprint)`的关系
  • 从0到1吃透卷积神经网络(CNN):原理与实战全解析
  • Linux安全第三章-系统安全及应用
  • vscode优化使用体验篇(快捷键)
  • 【Leetcode】取余/2的幂次方
  • elasticsearch kibana ik 各版本下载
  • Java API学习笔记
  • iOS APP启动页及广告页的实现
  • Python打卡DAY29
  • 架构设计模式:构建健壮、可扩展的 Serverless 应用
  • Seata源码—6.Seata AT模式的数据源代理一
  • buck变换器的simulink/matlab仿真和python参数设计
  • 【AGI】大模型微调数据集准备
  • Nginx配置中include mime.types的作用及正确配置mime类型
  • 不同版本 Linux 系统账号操作指令 ——rtkit 账号删除、普通账号的创建 / 删除 / 权限修改超详细大全
  • 【深度学习】使用块的网络(VGG)
  • MyBatis 延迟加载与缓存
  • 10.7 LangChain v0.3架构大升级:模块化设计+多阶段混合检索,开发效率飙升3倍!
  • 厦门知名做企业网站设计的公司/免费建站的网站哪个好
  • 云南网络宣传公司/谷歌seo优化
  • 境外网址app/seo快速排名多少钱
  • 中山建网站最好的公司/世界新闻最新消息
  • 忠县网站建设/宽带业务如何推广
  • vue响应式网站开发/竞价推广平台