CLR 通过 C# 实现的“互锁任何模式”不是线程安全的吗

问题描述 投票:0回答:1

阅读 Jeffrey Richter 在 CLR 中通过 C# 第 4 版创造的 Interlocked Anything 模式时,他给出了以下示例,证明我们可以为

Add
 做的不仅仅是 
Interlocked

public static Int32 Maximum(ref Int32 target, Int32 value)
{ 
    Int32 currentVal = target, startVal, desiredVal; 
     
    // Don't access target in the loop except in an attempt 
    // to change it because another thread may be touching it 
    do
    { 
        // Record this iteration's starting value 
        startVal = currentVal; 
     
        // Calculate the desired value in terms of startVal and value 
        desiredVal = Math.Max(startVal, value); 
     
        // NOTE: the thread could be preempted here! 
     
        // if (target == startVal) target = desiredVal 
        // Value prior to potential change is returned 
        currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); 
     
        // If the starting value changed during this iteration, repeat 
    } while (startVal != currentVal); 
     
    // Return the maximum value when this thread tried to set it 
    return desiredVal; 
}

对于 C# 编译器为事件生成的

add
remove
方法,可以找到非常相似的模式版本:

[CompilerGenerated]
{
    EventHandler eventHandler = this.MyEvent;
    EventHandler eventHandler2;
    do
    {
        eventHandler2 = eventHandler;
        EventHandler value2 = (EventHandler)Delegate.Combine(eventHandler2, value);
        eventHandler = Interlocked.CompareExchange(ref this.MyEvent, value2, eventHandler2);
    }
    while ((object)eventHandler != eventHandler2);
}

然而,一个很大的区别是我们如何获取“目标”值 - ref 参数与字段访问。

使用 ref 参数的原始示例,在通过以下方式将目标值分配给临时值之前如何防止目标值发生更改:

Int32 currentVal = target, startVal, desiredVal; 

例如,当我们跳转到方法的开头时,另一个线程更改值?

c# multithreading lock-free interlocked
1个回答
0
投票

这里的关键点是

Interlocked.CompareExchange
;这是一种非常著名的模式,其中代码获取当前值的快照,计算预期的新值,然后使用
Interlocked.CompareExchange
执行条件更新 且仅当 目标与我们的快照匹配,即该值没有改变。如果它发生了变化,那么我们将使用更新的值快照重复从第一个主体开始的所有操作。因此,代码将不会离开此循环,直到 if 设法完成一次迭代,其中值在快照和更新之间不会更改线程安全很复杂,您需要指定针对什么风险

代码是安全的;我们

可以可靠地说,这种机制确保我们永远不会因为未观察到的冲突更新而出现错误 - 例如,由于这是一个最大值操作,因此尽管另一个线程更新该值,我们也永远不会从 7 变为 14到 25(导致 25 被错误地丢弃)。所以;线程安全避免碰撞:是的。然而,如果我们是迂腐的,我们就不得不承认,无法保证这可能需要多长时间 - 理论上,如果并发之神如此希望的话,它可能会永远循环 - 或者我猜最多 40 亿次,因为它是一个32位整数。所以从另一个角度来说,它很容易受到碰撞副作用的影响而影响性能。实际上,这几乎从来都不是一个真正的问题,在大多数常见用例中,甚至循环两次也不太可能。当它一个真正的问题时,通常意味着某些内容更新过于频繁,可以通过让工作循环仅定期而不是每次迭代更新共享令牌来避免这种情况。

© www.soinside.com 2019 - 2024. All rights reserved.