C++基于std::atomic实现的自定义锁偶发死锁原因排查与修复
自定义同步锁偶发死锁问题分析与修复
死锁根本原因
1. 自定义锁内部逻辑漏洞(核心诱因)
get_lock方法时序错误导致丢失唤醒
原代码中CAS操作失败后,先将expected_tmp重置为目标预期值,再调用wait,存在严重的时序窗口:
当线程CAS失败、重置expected_tmp之后、调用wait之前,持有锁的线程可能已经完成释放锁操作并发送了notify_all,此时notify信号会被直接丢弃。后续线程进入wait时锁状态刚好等于预期值,会永久阻塞,没有新的notify信号唤醒,直接触发死锁。release_lock释放操作问题
原代码直接使用=对原子变量赋值,虽然std::atomic的store操作本身是原子的,但未显式控制内存序的场景下,极端情况可能出现指令重排,导致锁释放前的临界区操作未完成就对外暴露了锁可用的状态,用exchange替换直接赋值可以明确保证操作的原子性和内存可见性。
2. 锁作用域设计不合理
原代码将锁对象定义在while循环开头,锁的持有范围覆盖了sleep_for休眠、终止状态检查等非临界区操作,锁持有时间过长,既会导致线程饥饿,也放大了上述逻辑漏洞的触发概率。
修复方案
1. 修正get_lock逻辑顺序
CAS失败后先调用wait等待锁状态变化,唤醒后再重置预期值进入下一轮CAS,避免丢失唤醒:
auto get_lock() -> void{ bool expected_tmp= expected_input; const bool new_object_value = !expected_tmp; while (!object.compare_exchange_strong(expected_tmp, new_object_value)) { // CAS失败时expected_tmp已经被更新为object当前的实际值,直接传入wait等待状态变化 object.wait(expected_tmp); // 唤醒后再重置预期值,准备下一轮CAS expected_tmp= expected_input; } got_lock = true; }
2. 优化锁释放逻辑
使用exchange替换直接赋值,保证释放操作的原子性和内存可见性:
auto release_lock() -> void{ if (!got_lock || already_released) { return; } // 用exchange显式执行原子写,也可指定memory_order_release保证内存序 object.exchange(expected_input, std::memory_order_release); object.notify_all(); already_released = true; }
3. 缩小锁作用域
仅在需要同步的临界区代码段内持有锁,非临界区操作(如休眠、终止状态检查)放在锁作用域之外:
while (1) { // 仅临界区持有锁 { bool expected_value = false; Lock_free_sync lock(flag, expected_value); printer(i, " >>> in loop working <<<<<<<<<<<<<<<<<"); ++count; printer(i, " >>> flag is", flag); } // 锁在此处自动释放 this_thread::sleep_for(1s); printer(i, " >>> check terminate"); if (terminate) { printer2(i, "stop thread", count); return; } printer(i, "about to reloop"); }
经过以上修改后,代码即可避免偶发死锁问题,并发运行稳定性和效率都会明显提升。
内容的提问来源于stack exchange,提问作者Kroma
相关产品推荐
相关产品推荐

