Thread.VolatileRead()与Volatile.Read()VolatileRead、Thread、Read、Volatile

2023-09-03 08:46:00 作者:吃可爱长大

我们被告知preFER Volatile.Read在Thread.VolatileRead在大多数情况下,由于后者发射全栅栏,前者仅发射的相关半围栏(例如取得围栏);这是更有效的。

We are told to prefer Volatile.Read over Thread.VolatileRead in most cases due to the latter emitting a full-fence, and the former emitting only the relevant half-fence (e.g. acquire fence); which is more efficient.

不过,在我的理解, Thread.VolatileRead 实际上提供的东西, Volatile.Read 不会,因为实施 Thread.VolatileRead

However, in my understanding, Thread.VolatileRead actually offers something that Volatile.Read does not, because of the implementation of Thread.VolatileRead:

public static int VolatileRead(ref int address) {
  int num = address;
  Thread.MemoryBarrier();
  return num;
}

在实施的第二行的全部记忆障碍,因为,我相信 VolatileRead 实际上保证值最后写入地址将被读取。 据Wikipedia, 全栅栏确保所有加载和存储操作之前,围栏将之前已经发出下面的栅栏任何加载和存储承诺。的。

Because of the full memory barrier on the second line of the implementation, I believe that VolatileRead actually ensures that the value last written to address will be read. According to Wikipedia, "A full fence ensures that all load and store operations prior to the fence will have been committed prior to any loads and stores issued following the fence.".

我的理解是否正确? ,因此,不会 Thread.VolatileRead 仍然提供一些 Volatile.Read 不?

Is my understanding correct? And therefore, does Thread.VolatileRead still offer something that Volatile.Read does not?

推荐答案

我可能有点晚了比赛,但我还是想附和。首先,我们需要达成一些基本定义。

I may be a little late to the game, but I would still like to chime in. First we need to agree on some basic definitions.

收购围栏:在其他的读取和写入操作是不允许的栅栏前,移动内存屏障 发布围栏:在其他的读取和写入,不允许栅栏后移动内存屏障

我喜欢用一个箭头符号来帮助说明行动的围栏。一个↑箭头将重新present释放栅栏和↓箭头会重新present某个acquire栅栏。想的箭头头,如在箭头的方向推动存储器访问远的。但是,这是很重要的,存储器的访问可移动经过尾。看了上面的栅栏的定义,并说服自己,箭头在视觉上重新present这些定义。

I like to use an arrow notation to help illustrate the fences in action. An ↑ arrow will represent a release-fence and a ↓ arrow will represent an acquire-fence. Think of the arrow head as pushing memory access away in the direction of the arrow. But, and this is important, memory accesses can move past the tail. Read the definitions of the fences above and convince yourself that the arrows visually represent those definitions.

使用这个符号让我们分析从 JaredPar的开始 Volatile.Read 。但是,首先让我如此地步, Console.WriteLine 的也许的生产全围栏阻隔瞒着我们。我们应该pretend了一会儿,它不使例子更容易理解。其实,我只是省略了通话完全,因为它是没有必要的是我们正在努力实现的范围内。

Using this notation let us analyze the examples from JaredPar's answer starting with Volatile.Read. But, first let me make the point that Console.WriteLine probably produces a full-fence barrier unbeknownst to us. We should pretend for a moment that it does not to make the examples easier to follow. In fact, I will just omit the call entirely as it is unnecessary in the context of what we are trying to achieve.

// Example using Volatile.Read
x = 13;
var local = y; // Volatile.Read
↓              // acquire-fence
z = 13;

因此​​,使用箭头符号,我们更容易看到写以Z 不能向上和 y的读前。也不能将写向下移动,而读出之后,因为这将有效地相同的其他方式。然而,和写 X 的读取可以调换,因为没有箭头preventing这种运动。同样,在写 X 可移过箭的尾部,甚至过去在写以Z 。该规范在技术上允许that..theoretically安韦。这意味着,我们有以下的有效排序。

So using the arrow notation we more easily see that the write to z cannot move up and before the read of y. Nor can the write move down and after the read because that would be effectively same as the other way around. However, the read of y and the write to x can be swapped as there is no arrow head preventing that movement. Likewise, the write to x can move past the tail of the arrow and even past the write to z. The specification technically allows for that..theoretically anway. That means we have the following valid orderings.

Volatile.Read
---------------------------------------
write x    |    read y     |    read y
read y     |    write x    |    write z
write z    |    write z    |    write x

现在让我们进入到这个例子与 Thread.VolatileRead 。为了这个例子中,我将内联调用 Thread.VolatileRead 来使它更容易显现。

Now let us move on to the example with Thread.VolatileRead. For the sake of the example I will inline the call to Thread.VolatileRead to make it easier to visualize.

// Example using Thread.VolatileRead
x = 13;
var local = y; // inside Thread.VolatileRead
↑              // Thread.MemoryBarrier / release-fence
↓              // Thread.MemoryBarrier / acquire-fence
z = 13;

仔细一看。没有方向(因为没有记忆障碍)的写入之间 X 的读取。这意味着这些内存访问是仍然可以自由地相对四处移动到对方。然而,在调用 Thread.MemoryBarrier ,产生了额外的释放栅栏,使得它看起来好像下一个内存访问过性写语义。这意味着写入 X 以Z 再也不能交换。

Look closely. There is no arrow (because there is no memory barrier) between the write to x and the read of y. That means these memory accesses are still free to move around relative to each other. However, the call to Thread.MemoryBarrier, which produces the additional release-fence, makes it appear as if the next memory access had volatile write semantics. This means the writes to x and z can no longer be swapped.

Thread.VolatileRead
-----------------------
write x    |    read y
read y     |    write x
write z    |    write z

当然,它一直声称,微软实施的CLI(.NET框架)和x86硬件已保证释放栅栏语义所有的写操作。因此,在这种情况下,有可能无法在两个电话之间的任何差异。在具有单声道的ARM处理器?事情可能会在这种情况下不同的。

Of course it has been claimed that Microsoft's implementation of the CLI (the .NET Framework) and the x86 hardware already guarantee release-fence semantics for all writes. So in that case there may not be any difference between the two calls. On an ARM processor with Mono? Things might be different in that case.

现在让我们继续前进的答案。

Let us move on now to your questions.

由于上的第二行的全部记忆障碍   实施,我相信VolatileRead实际上确保了   值最后写入地址将被读取。我的理解是   是否正确?

Because of the full memory barrier on the second line of the implementation, I believe that VolatileRead actually ensures that the value last written to address will be read. Is my understanding correct?

否。这是不正确的!挥发性读是不一样的一个鲜读。为什么?这是因为内存屏障被放置的在的读指令。这意味着实际的读操作仍然可以自由地向上或向后的时间。另一个线程可以写的地址,但当前线程可能已经的前移动的读取到的时间点的其它线程提交它。

No. This is not correct! A volatile read is not the same as a "fresh read". Why? It is because the memory barrier is placed after the read instruction. That means the actual read is still free to move up or backwards in time. Another thread could write to the address, but the current thread might have already moved the read to a point in time before that other thread committed it.

因此​​,这引出了一个问题,为什么人们懒得使用挥发性读取,如果它看似保证这么少?。答案是,绝对保证下一个读会的更新的超过previous阅读。这就是它的价值!这就是为什么很多无锁code旋转的循环,直到逻辑可以确定该操作已成功完成。换句话说,无锁code利用了以后要阅读很多序列读取将返回的概念的更新的价值,但code不应承担任何的读取必然重新present中的最新的的值。

So this begs the question, "Why do people bother using volatile reads if it seemingly guarantees so little?". The answer is that it absolutely guarantees that the next read will be newer than the previous read. That is its value! That is why a lot of lock-free code spins in a loop until the logic can determine that the operation was completed successfully. In other words, lock-free code exploits the concept that the later read in a sequence of many reads will return a newer value, but the code should not assume that any of the reads necessarily represent the latest value.

想一想一分钟。这是什么意思,甚至为读反正返回最新值?通过使用这个值的时候,可能不是最新的了。另一个线程可能已经写了一个不同的值,以相同的地址。你仍然可以调用该值的最新消息?只是一些精神食粮......

Think about this for a minute. What does it even mean for a read to return the latest value anyway? By the time you use that value it might not be the latest anymore. Another thread may have already written a different value to the same address. Can you still call that value the latest? Just some food for thought...

,因此,不会Thread.VolatileRead仍然提供的东西,   Volatile.Read不?

And therefore, does Thread.VolatileRead still offer something that Volatile.Read does not?

是。我认为JaredPar presented在那里可以提供一些额外的情况的一个很好的例子。

Yes. I think JaredPar presented a perfect example of a case where it can offer something additional.