在使用Interlocked.Exchange之后如何无锁读取值?

12 浏览
0 Comments

在使用Interlocked.Exchange之后如何无锁读取值?

假设我们有一个类,如下所示:

public class Foo
{
    private Bar bar = new Bar();
    
    public void DoStuffInThread1()
    {
        var old = Interlocked.Exchange(ref bar, new Bar());
        // 使用旧的bar进行操作
        // 这里没问题,我确保拿到了之前的bar值
    }
    
    public void OtherStuffFromThread2()
    {
        // 如何确保我在这里拿到的是最新的bar引用
        // 考虑到内存缓存等情况
        bar.Something();
    }
}

假设我们有两个线程,一个在执行`DoStuffInThread1`方法,另一个在执行`OtherStuffFromThread2`方法。

如何确保线程2始终看到最新的`bar`引用?

`volatile`修饰符无法解决此问题,而我又不想使用旧式的锁。

在某种程度上,通过内存屏障/原子操作等方式可以读取到正确的`bar`值吗?

0
0 Comments

Lockfree Read value after Interlocked.Exchange?

在多线程编程中,经常会遇到需要读取最新值的情况。这时候,我们可以使用类似于Interlocked.CompareExchange(ref bar, null, null)的方法来实现。检查bar是否为null只是为了满足CompareExchange的签名要求(如果barnull,则将其设置为null)。这样可以确保我们获取到的是在执行时各个CPU之间最新的可用值。

问题的出现原因是:在多线程环境中,由于各个线程的执行顺序是不确定的,有可能在某个线程执行到Interlocked.Exchange之后,其他线程还没有更新bar的值。这就导致了在读取bar时可能获取到的是旧值而不是最新值。

解决方法是使用Interlocked.CompareExchange(ref bar, null, null)。这个方法可以确保在多线程环境中获取到的是最新的可用值。通过比较barnull的值,如果相等则将bar设置为null,然后返回bar的旧值。这样可以确保我们在执行时获取到的是各个CPU之间最新的可用值。

总之,要在多线程编程中实现无锁读取在Interlocked.Exchange之后的值,可以使用Interlocked.CompareExchange方法。这样可以确保在多线程环境中获取到的是最新的可用值,避免了读取到旧值的问题。

0
0 Comments

Lockfree Read value after Interlocked.Exchange?

问题的出现原因:

在代码中,作者在OtherStuffFromThread2()方法中想要确保在调用bar.Something()之前拿到最新的bar引用。作者尝试使用各种方法来等待thread1准备好,但是这些方法都会导致隐式的内存屏障(memory barrier)。这种内存屏障会使得bar.Something()之前的内存操作无法被重排。

解决方法:

为了确保读取变量的最新值,可以先读取一个volatile变量,然后再读取目标变量(或者重复读取同一个volatile变量)。为什么呢?因为volatile读操作具有获取语义(acquire semantics),这意味着它不会与后续的内存操作进行重排。可以使用如下代码实现:

private static int myuselessvolatilefieldthatcanbestatic;
private int thefieldiwanttoread;

然后:

var useless = myuselessvolatilefieldthatcanbestatic;
var iwanttoknow = thefieldiwanttoread;

这样,thefieldiwanttoread将包含在对myuselessvolatilefieldthatcanbestatic进行新的读取操作之后读取到的值。

需要注意的是,没有同步机制的情况下,很难知道何时完成myuselessvolatilefieldthatcanbestatic的读取。但是可以使用如下代码结构:

while (true)
{
    var useless = myuselessvolatilefieldthatcanbestatic;
    var iwanttoknow = thefieldiwanttoread;
    // Some code goes here
}

现在至少可以在while (true)循环中使用这段代码了。

0