有读屏障和写屏障;获得障碍和释放障碍。还有更多(io 与内存等)。
不存在控制“最新”值或值的“新鲜度”的障碍。它们的作用是控制内存访问的相对顺序。
写屏障控制写入的顺序。由于写入内存的速度很慢(与 CPU 的速度相比),因此通常有一个写入请求队列,写入在“真正发生”之前就被发布到其中。尽管它们按顺序排队,但在队列内部,写入可能会重新排序。 (所以也许“队列”不是最好的名字......)除非您使用写屏障来防止重新排序。
读屏障控制读取的顺序。由于推测执行(CPU 提前查看并提前从内存加载)并且由于写入缓冲区的存在(CPU 将从写入缓冲区而不是内存中读取值(如果存在),即 CPU 认为它刚刚写入了 X = 5,那为什么要读回来呢,就看它还在等待become5)读取可能会发生乱序。
无论编译器尝试对生成代码的顺序执行什么操作,情况都是如此。即 C++ 中的“易失性”在这里不起作用,因为它只告诉编译器输出代码以重新读取“内存”中的值,它不会告诉 CPU 如何/从哪里读取它(即“内存”) CPU 级别上有很多事情)。
因此,读/写屏障会设置块来防止读/写队列中的重新排序(读取通常不是一个队列,但重新排序效果是相同的)。
有哪些类型的块? - 获取和/或释放块。
获取 - 例如 read-acquire(x) 会将 x 的读取添加到读取队列中并刷新队列(并不是真正刷新队列,而是添加一个标记,说明在读取之前不要重新排序任何内容,就像刷新队列一样)。因此稍后(按代码顺序)读取可以重新排序,但不能在读取 x 之前重新排序。
释放 - 例如 write-release(x, 5) 将首先刷新(或标记)队列,然后将写入请求添加到写入队列。因此,较早的写入不会在 x = 5 之后重新排序,但请注意,稍后的写入可以在 x = 5 之前重新排序。
请注意,我将读取与获取配对,将写入与释放配对,因为这是典型的,但也可能有不同的组合。
获取和释放被视为“半屏障”或“半栅栏”,因为它们仅阻止重新排序以一种方式进行。
完整的屏障(或完整的栅栏)同时应用获取和释放 - 即没有重新排序。
通常对于无锁编程,或 C# 或 java“易失性”,您想要/需要的是
读-获取和写-释放。
ie
void threadA()
{
foo->x = 10;
foo->y = 11;
foo->z = 12;
write_release(foo->ready, true);
bar = 13;
}
void threadB()
{
w = some_global;
ready = read_acquire(foo->ready);
if (ready)
{
q = w * foo->x * foo->y * foo->z;
}
else
calculate_pi();
}
因此,首先,这是一种糟糕的线程编程方式。有锁会更安全。但只是为了说明障碍......
threadA() 写完 foo 后,它需要最后写入 foo->ready,真的是最后,否则其他线程可能会提前看到 foo->ready 并得到错误的 x/y/z 值。所以我们使用一个write_release
在 foo->ready 上,如上所述,它有效地“刷新”写入队列(确保 x、y、z 已提交),然后将 ready=true 请求添加到队列中。然后添加 bar=13 请求。请注意,由于我们刚刚使用了释放屏障(不是完整的),所以 bar=13 可能会在准备好之前被写入。但我们不在乎!即我们假设 bar 没有更改共享数据。
现在 threadB() 需要知道,当我们说“准备好”时,我们真正的意思是准备好。所以我们做了一个read_acquire(foo->ready)
。该读取被添加到读取队列中,然后刷新队列。注意w = some_global
也可能仍在队列中。所以 foo->ready 可以被读取before some_global
。但同样,我们不在乎,因为它不是我们如此关注的重要数据的一部分。
我们关心的是 foo->x/y/z。所以它们在acquireflush/marker之后被添加到读队列中,保证它们只有在读取foo->ready之后才被读取。
另请注意,这通常与用于锁定和解锁互斥体/关键部分/等的屏障完全相同。 (即在lock()上获取,在unlock()上释放)。
So,
我很确定这(即获取/释放)正是 MS 文档所说的 C# 中“易失性”变量的读/写发生的情况(也可以选择 MS C++,但这是非标准的)。看http://msdn.microsoft.com/en-us/library/aa645755(VS.71).aspx包括“易失性读取具有“获取语义”;也就是说,它保证发生在其之后发生的任何对内存的引用之前......”
I thinkjava也是一样,虽然我不太熟悉。我怀疑它是完全相同的,因为您通常不需要比读取获取/写入释放更多的保证。
在你的问题中,当你认为这实际上都是关于相对顺序时,你是在正确的轨道上 - 你只是向后排序(即“读取的值至少与屏障之前的读取一样最新? “ - 不,屏障之前的读取并不重要,其在屏障之后的读取保证在屏障之后,对于写入反之亦然)。
请注意,如上所述,读取和写入都会发生重新排序,因此仅在一个线程上使用屏障而不在另一个线程上使用屏障将不起作用。即,如果没有读取获取,写入释放是不够的。即,即使您以正确的顺序写入它,如果您没有将读取屏障与写入屏障一起使用,也可能会以错误的顺序读取它。
最后,请注意,无锁编程和 CPU 内存架构实际上可能比这复杂得多,但坚持获取/释放会让你走得更远。