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,计算 sum=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. 锁粒度不合理
    粗粒度锁(如全局锁)会降低并发性能,细粒度锁(如分段锁)若设计不当则可能遗漏保护关键区域,两者均可能间接引发数据竞争。

  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博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值