释放-获取顺序
在强序系统(x86、SPARC、IBM大型机)上,自动执行释放-获取顺序。对于此同步模式,不会发出任何其他CPU指令,只会影响某些编译器优化...
首先,x86是否遵循严格的内存顺序?似乎总是强制执行这一点非常低效。这意味着每个写和读操作都需要一个栅栏吗?
另外,如果我有一个对齐的int,在x86系统上,原子变量是否有任何作用?
没错,x86架构有严格的内存顺序,参见Intel手册第3A卷第8.2章。早期的x86处理器(如386)提供了真正严格的顺序(称为强顺序)语义,而更现代的x86处理器在少数情况下略微放松了一些条件,但这些都不是您需要担心的问题。例如,Pentium和486允许读高速缓存未命中的操作比缓冲写先执行,当写是高速缓存命中(因此与读的地址不同)时。
是的,这可能效率低下。有时,由于此原因,高性能软件仅针对具有较松散内存排序要求的其他架构编写。
是的,在x86上原子变量仍然有用。它们具有特殊的语义,可以使典型的读-修改-写操作在原子级别进行。如果您有两个线程同时增加原子变量(我指的是在C++11中类型为std::atomic<T>
的变量),则可以确保值将增加2;如果没有std::atomic
,您可能会得到错误的值,因为一个线程在执行增量时将当前值缓存在寄存器中,即使在x86上对内存的存储是原子的。
在x86架构中,所有存储操作都具有释放语义,而所有加载操作都具有获取语义,这是真实的。
但这不会且不应该影响你编写C++的方式:为了编写并发、无竞争的代码,你必须使用std::atomic
结构或锁。
架构细节的意义在于,在x86上,只要您最多要求获取/释放排序,对原子字大小类型的操作就几乎不会产生额外的代码(顺序一致性会生成mfence
指令)。然而,你仍然必须使用C++的原子类型来编写正确且符合规范的程序。原子变量的一个重要特性是它们可以防止编译器重新排序,这对于程序的正确性至关重要。
(在C++11之前,你需要使用编译器提供的扩展,例如GCC的__sync_*
函数套件,来让编译器正确执行。如果你真的想使用裸露的变量,你至少需要自己插入编译器屏障。)
mfence
是双向完全内存栅栏。它非常昂贵,这就是为什么你总是希望尽可能地松弛到 a/r(在 x86 上是免费的)。 - Kerrek SB#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
void producer()
{
std::string* p = new std::string("Hello");
ptr = p;
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr))
;
assert(*p2 == "Hello"); // never fails
}
(在x86上)使用(g++ -std=c++11 -S -O3)实际上会在生产者函数中发出一个mfence
,以解决上述松弛问题()。
而对于...
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
void producer()
{
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fails
}
有一个很好的表格列出了不同的重新排序操作,这些操作可能会发生,例如x86几乎不进行任何操作。其他架构(尤其是Alpha)则几乎可以进行任何操作。
对于由标准定义的内存模型,x86等架构本质上是符合规范的。
关于原子变量的问题有稍微不同的答案。对变量的任何修改都涉及竞争条件,因此当多个线程更新相同的变量时,更新可能会丢失。原子变量被定义为具有原子操作的正确类型,这些操作消除了这种竞争条件。因此,它们的目的之一不仅仅是用于排序。
volatile
关键字只能在某些编译器中起作用(作为扩展)。这就是为什么标准添加了atomic<>
的原因。 - Bo Perssonvolatile
并不一定能使读-改-写操作具有原子性。如果你对一个volatile
变量执行x++
操作,编译器可以将x
读入寄存器,增加寄存器的值,然后将寄存器的值存回内存中。 - Adam Rosenfieldvolatile
的可观察行为必须是单独的读和写。 - MSaltersoperator++
。你不能使用std::atomic<T>
写x=x+1
,因为这些对象不可复制分配,原因正是这样做是非原子的。 - Adam Rosenfield