CLR via C#中的“Interlocked通用模式”是否非线程安全?
Interlocked通用模式的原子性疑问解答
核心结论
这个模式并没有失去原子性,它的原子性体现在整个CAS(CompareExchange)循环的重试逻辑上——单次读取target的操作确实是非原子的,但循环机制保证了最终的更新操作是线程安全且无丢失更新的。
先明确模式的核心逻辑
Jeffrey Richter在《CLR via C#》第4版中给出的Interlocked通用Maximum示例代码大致如下:
public static void Maximum(ref Int32 target, Int32 value) { Int32 currentVal = target; while (currentVal < value) { Int32 oldVal = Interlocked.CompareExchange(ref target, value, currentVal); if (oldVal == currentVal) break; currentVal = oldVal; } }
这个逻辑的关键是CAS循环:
- 先读取
target的当前值到currentVal - 判断是否需要执行更新(这里是当
currentVal小于传入的value时) - 调用
CompareExchange:仅当target的当前值与currentVal完全一致时,才将其替换为value - 如果CAS操作失败(返回的
oldVal不等于currentVal,说明其他线程已修改target),就用最新的oldVal更新currentVal,重新进入循环重试
关于LINQPad示例中返回100的解释
你遇到的Maximum返回其他线程修改后的100,恰恰是模式正常工作的表现:
- 线程A读取
target为某个值(比如50) - 线程B抢先将
target修改为100 - 线程A执行CAS时,发现
target已不是50,于是获取到最新的100作为currentVal - 由于100不小于传入的目标值(假设是80),循环直接退出,最终
target保留100——这正是该模式的设计目的:确保target最终是所有线程操作后的最大值,不会出现并发修改导致的更新丢失。
与事件add/remove实现的差异
C#编译器生成的事件add/remove方法采用了类似的CAS循环,但直接访问字段而非使用ref参数。两者的核心逻辑完全一致:都是通过CAS重试来处理并发修改,参数传递方式的差异不影响原子性的本质。
误区澄清
你提到的Int32 currentVal = target操作非原子是事实,但这个模式的原子性并非指单次读取-修改-写入的原子性,而是指最终的更新操作是原子性的,且不会丢失任何线程的修改意图。CAS循环的意义就在于:如果在读取和更新之间有其他线程修改了值,就重新读取最新值再尝试,直到成功或无需更新为止。
简言之,这个模式保证的是「要么基于最新值完成更新,要么重试」,不会出现多线程并发修改导致的中间值丢失问题。
内容的提问来源于stack exchange,提问作者Ivan Petrov
相关产品推荐
相关产品推荐

