如何在C++中用原子变量实现互斥锁?解决银行账户取款线程同步问题
解决原子变量取款时的余额负数问题 & 用原子变量实现互斥锁
嘿,我完全懂你遇到的这个坑——你现在的取款逻辑里,余额检查和后续的原子更新不是一个原子操作,这就给了竞态条件可乘之机:两个线程都先读到了300的余额,都觉得够取200,然后先后执行compare_exchange,第二个线程就会把已经变成100的余额再减200,直接搞成负数。
下面给你两种解决方案,一种是更高效的原子操作优化方案(不用锁,从根源上解决竞态),另一种是用原子变量实现自旋锁(满足你“用原子变量当互斥锁”的需求):
方案一:把余额检查+更新合并为原子循环操作
这是最优解,不需要额外的锁,直接利用compare_exchange的循环特性,把余额检查和更新绑定在一个原子操作的重试逻辑里:
void withdraw(int amount) { float old_balance; do { old_balance = m_iBalance.load(); // 第一步:先检查当前余额是否足够,不够直接退出 if (old_balance < amount) { std::cout << "余额不足,无法取出 " << amount << std::endl; return; } // 第二步:尝试原子更新,只有当当前余额和old_balance一致时才会成功 // 如果失败,说明其他线程已经修改了余额,old_balance会被自动更新为最新值,进入下一轮循环 } while (!m_iBalance.compare_exchange_weak(old_balance, old_balance - amount)); // 走到这里说明取款成功,余额肯定是非负的 }
为什么这个能解决问题?
每次循环都会重新读取最新的余额,然后检查是否足够。如果在检查之后、更新之前,其他线程修改了余额,compare_exchange_weak会失败,同时自动把old_balance更新为当前的最新余额,下一轮循环就会用新的余额重新检查,不会再出现“两个线程都基于旧余额执行减法”的情况。
方案二:用原子变量实现自旋锁(模拟互斥锁)
如果你确实需要用原子变量来做互斥,让同一时间只有一个线程进入取款函数,可以用一个原子布尔变量实现自旋锁:
#include <atomic> #include <iostream> #include <thread> // 用于std::this_thread::yield() std::atomic<bool> g_withdraw_lock = false; // 锁标志,false表示未锁定 std::atomic<float> m_iBalance; void withdraw(int amount) { // 自旋等待获取锁:不断尝试把锁从false改成true,成功则获取到锁 while (!g_withdraw_lock.compare_exchange_weak(false, true)) { // 可以加yield()让出CPU,减少空循环的资源浪费 std::this_thread::yield(); } // 临界区:这里同一时间只有一个线程能进入 const float current_balance = m_iBalance.load(); if (current_balance >= amount) { m_iBalance.store(current_balance - amount); } else { std::cout << "余额不足,无法取出 " << amount << std::endl; } // 释放锁:把标志改回false g_withdraw_lock.store(false); }
注意事项
- 自旋锁在高并发场景下可能会浪费CPU资源(线程空循环等待),如果是单核心CPU,不如直接用
std::mutex更高效;但多核心场景下,自旋锁的开销比互斥锁小。 - 一定要确保锁被正确释放,比如如果取款函数里有异常,要记得用RAII封装锁(比如自己写个简单的LockGuard类),避免死锁。
总结
优先推荐方案一,因为它更高效,也更符合原子变量的设计初衷;方案二是满足你“用原子变量当互斥锁”需求的实现方式,但要注意自旋锁的适用场景。
内容的提问来源于stack exchange,提问作者Ettur
相关产品推荐
相关产品推荐

