You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何带局部作用域的std::lock_guard仍能保证线程安全?

线程安全误区解析:为什么看似未保护的i++结果总是2000?

嘿,这个问题我刚看到的时候也有点疑惑,毕竟按理论来说,你的理解是对的——这段代码里的i++确实没有被互斥锁保护,理论上应该存在线程安全问题,可能出现i最终小于2000的情况。但实际运行总是得到2000,其实是环境和测试条件导致的巧合,咱们一步步拆解:

先看你的代码

mutex m; 
void thread_function() { 
    static int i = 0; 
    for(int j =0; j<1000; j++) { 
        { // 你认为这个作用域会让锁立即释放
            lock_guard<mutex> lock(m); 
        } 
        i++; 
        cout<<i<<endl; 
    } 
}

你的核心理解是对的,但结果是巧合

你说的没错:lock_guard在那个局部作用域里,进入时加锁,离开作用域就自动释放锁。所以i++和cout<<i完全是在无锁状态下执行的,而i++是读-改-写的非原子操作,两个线程同时执行时,完全可能出现丢失更新的情况(比如线程A读了i=5,线程B也读了i=5,然后各自加1,最终i变成6而不是7)。

那为什么实际运行总是得到2000?可能有这几个原因:

  • 循环次数太少:1000次循环的操作非常快,操作系统的线程调度可能还没来得及在i++的执行间隙切换线程,导致两个线程几乎是串行执行完所有循环,自然不会出现竞争。
  • cout的隐性同步:标准输出流cout默认是线程安全的(不同线程的输出不会交错),虽然它不会保护i的读写,但cout<<i的操作本身会有一定的同步开销,间接让线程切换的概率降低,减少了竞争发生的可能。
  • 线程调度策略:如果你的系统是单核心CPU,线程是分时复用的,可能刚好一个线程跑完所有循环,另一个才开始,完全没有并发执行的场景。

但这些都是偶然情况,这段代码本质上是线程不安全的。你可以试试把循环次数改成1000000,或者在i++后面加个短时间的延迟(比如this_thread::sleep_for(chrono::nanoseconds(1));),大概率会看到i的最终值小于2000。

正确的线程安全写法

要确保i最终一定是2000,需要把所有涉及i的读写操作都放在锁的保护范围内:

mutex m; 
void thread_function() { 
    static int i = 0; 
    for(int j =0; j<1000; j++) { 
        lock_guard<mutex> lock(m); 
        i++; 
        cout<<i<<endl; 
    } 
}

这样i++和cout<<i都在锁的作用域内,每次只有一个线程能执行这两个操作,彻底避免竞争。

内容的提问来源于stack exchange,提问作者Sridhar Thiagarajan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:20:52