2

我已经读过,在加载-修改-存储指令之后放置一个栅栏指令,比如 BTS,可以让您将第二个指令视为原子指令。但是根据英特尔的文档,围栏指令被描述为

(MFENCE)

对在 MFENCE 指令之前发出的所有从内存加载和存储到内存指令执行序列化操作。这种序列化操作保证了在程序顺序中位于 MFENCE 指令之前的每个加载和存储指令在 MFENCE 指令之后的任何加载或存储指令之前变得全局可见。

那么,这样的行为如何保证所提到的“原子性”呢?

具体来说,如果我们让不同的处理器同时运行以下代码,那么在这两种情况下,栅栏如何防止将 0 读入 CF?

start memory assumption: [addr] contains the word 0

BTS WORD PTR [addr], 0
MFENCE
4

1 回答 1

4

推一些栅栏并不足以赋予原子性。

对于单线程代码,它们并没有真正的好处,CPU 会知道对负载进行排序并在内部存储以实现正确的执行,因为它的核心是串行运行的(即使实际上,大多数现代 CPU 都会按顺序运行它)。

围栏的好处可能会出现在这样的场景中 -

thread1:                    |         thread 2:
    store [x],1             |             store [y],1
    load [y] -> r1          |             load [x] -> r2

这是内存一致性问题的典型示例 - 如果读取 2 个寄存器,程序员可能期望的结果是 1,1(两个存储首先发生,然后两个加载),或 1,0 或 0,1(如果其中一个线程跑在另一个之前。你没想到的是 0,0,因为至少有一个线程应该已经完成​​了写入。但是,通过宽松的内存排序,这可能是可能的 - 加载是在早期完成的管道,并且存储很晚。由于地址中没有线程内别名(假设 x!= y),CPU 没有采取任何措施来防止这种情况发生。

如下添加栅栏将保证如果其中一个线程达到负载,则前面的存储必须已被调度和​​观察。这意味着您仍然可以获得 0,1 和 1,0(如果两个 store-fence-load 首先在一个线程中完成),当然还有 1,1,但您不能再获得 0,0。

thread1:                    |         thread 2:
    store [x],1             |             store [y],1
    mfence                  |             mfence
    load [y] -> r1          |             load [x] -> r2

另请参阅 - http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/

但是,您要求原子性-这更强大,让我们举个例子-

BTS WORD PTR [addr], 0
MFENCE

如果我们将它复制到 2 个线程中,它基本上就像以前一样,除了栅栏在加载和存储之后(它们被分组到同一指令中的事实不会改变完成的基本操作)。是什么阻止您先进行两次读取,在两个线程上读取 0,然后再进行存储(这将涉及缓存中的一些 MESI 状态竞争,因为如果两个线程位于不同的内核上,它们将争夺所有权),但最终会导致两家商店都写入该行。然后你可以随心所欲地执行 mfences,这不会把你从已经破坏的原子性中拯救出来。

保证原子性的是一个很好的老式锁。即使以这种方式读取,线程也无法同时共享该行。它通常被认为是一种缓慢但必要的邪恶,但一些现代 CPU 甚至可能在硬件中优化它们!请参阅 - http://en.wikipedia.org/wiki/Transactional_Synchronization_Extensions

编辑:经过一番搜索,我相信导致这个问题的原因与 c++11 中原子关键字的定义方式有关。这些链接 - Concurrency: Atomic and volatile in C++11 memory modelhttp://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/表明一些实现是通过在商店后面推mfences。但是,我不认为这假装暗示对原子变量执行的任何常规(非库)操作都必然是原子的。无论如何,这个机制应该提供多种内存一致性模型,所以我们需要在这里更具体

EDIT2:似乎确实有一个大的“运动”(不知道如何称呼它们:)试图减少锁的必要性,这是一个有趣的部分: http: //preshing.com/20120612/an-introduction-to-无锁编程/ . 这主要是关于软件设计和能够区分真正潜在的数据竞争,但底线似乎是总是需要一些锁。c++11 的添加虽然使给定的一致性模型的工作更轻松,并且消除了程序员实现硬件特定解决方案的需要,但仍可能被迫落入旧解决方案。引用:Be aware that the C++11 atomic standard does not guarantee that the implementation will be lock-free on every platform

于 2013-09-27T21:44:22.240 回答