在使用Interlocked.Exchange之后如何无锁读取值?
在使用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`值吗?
Lockfree Read value after Interlocked.Exchange?
在多线程编程中,经常会遇到需要读取最新值的情况。这时候,我们可以使用类似于Interlocked.CompareExchange(ref bar, null, null)
的方法来实现。检查bar
是否为null
只是为了满足CompareExchange的签名要求(如果bar
是null
,则将其设置为null
)。这样可以确保我们获取到的是在执行时各个CPU之间最新的可用值。
问题的出现原因是:在多线程环境中,由于各个线程的执行顺序是不确定的,有可能在某个线程执行到Interlocked.Exchange
之后,其他线程还没有更新bar
的值。这就导致了在读取bar
时可能获取到的是旧值而不是最新值。
解决方法是使用Interlocked.CompareExchange(ref bar, null, null)
。这个方法可以确保在多线程环境中获取到的是最新的可用值。通过比较bar
与null
的值,如果相等则将bar
设置为null
,然后返回bar
的旧值。这样可以确保我们在执行时获取到的是各个CPU之间最新的可用值。
总之,要在多线程编程中实现无锁读取在Interlocked.Exchange之后的值,可以使用Interlocked.CompareExchange方法。这样可以确保在多线程环境中获取到的是最新的可用值,避免了读取到旧值的问题。
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)
循环中使用这段代码了。