无并发控制的系统中如何安全递减变量并避免重复执行?
解决方案:无锁/软件互斥算法实现并发安全
针对你描述的场景——无系统级同步原语(mutex/futex等)、所有操作可并行执行,我们可以通过无锁原子操作或软件层面的互斥算法来避免并发执行导致的错误,以下是具体实现:
一、基于CAS原子操作的无锁方案(优先推荐)
如果平台支持底层的**比较并交换(CAS, Compare-And-Swap)**原子指令(多数硬件平台都支持,比如x86的cmpxchg),可以用循环重试的方式实现原子性的修改与校验:
loop: # 原子读取当前var的值,确保拿到最新的内存值 current = atomic_read(var) new_val = current - 5 # 提前判断合法性,避免无效的CAS操作 if new_val < 0: abort() break # 尝试原子更新:只有当var当前值等于current时,才更新为new_val if atomic_cas(var, current, new_val): # 更新成功,执行支付 send_payment() break else: # 更新失败,说明其他线程已修改var,重试整个流程 continue
逻辑说明:
- 每个线程先原子读取
var的最新值,计算减5后的结果; - 若结果小于0,直接终止操作;
- 通过CAS尝试原子更新
var:如果此时var未被其他线程修改(仍为current),则更新成功并执行支付; - 若CAS失败,说明有其他线程抢先修改了
var,当前线程重新读取最新值并重试,直到操作成功或判定为非法。
这种方案无需阻塞,并发性能优于互斥锁,能完美避免你描述的“双线程读取相同初始值、重复支付”问题。
二、纯软件互斥算法(无原子操作时使用)
如果平台完全不支持任何原子操作,可通过软件层面的互斥逻辑实现临界区的串行执行,适用于双线程或多线程场景:
1. Peterson算法(双线程场景)
定义两个共享变量(需保证内存可见性,比如标记为volatile):
flag[2]:标记对应线程是否想要进入临界区turn:标记当前允许进入临界区的线程ID
线程0的代码:
flag[0] = true turn = 1 # 等待其他线程退出临界区,或轮到自己进入 while flag[1] and turn == 1: pass # 临界区:执行修改与支付逻辑 var -= 5 if var < 0: abort() else: send_payment() # 退出临界区,释放权限 flag[0] = false
线程1的代码(仅需互换0和1):
flag[1] = true turn = 0 while flag[0] and turn == 0: pass var -= 5 if var < 0: abort() else: send_payment() flag[1] = false
2. Lamport面包店算法(多线程场景)
适用于任意数量的线程,核心逻辑是“取号排队”:
- 每个线程先获取一个唯一的递增票号;
- 线程等待所有票号比自己小的线程完成临界区操作;
- 进入临界区执行操作,完成后释放票号。
该算法无需硬件原子操作,仅需保证内存操作的可见性,即可实现多线程下的互斥访问。
内容的提问来源于stack exchange,提问作者user2284570
相关产品推荐
相关产品推荐

