为何带局部作用域的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
相关产品推荐
相关产品推荐

