在C ++ 0x中的一个原子变量文档中,当描述内存顺序时,它提到:
发布 - 获取订购
在强烈排序的系统(x86,SPARC,IBM大型机)上,发布 - 获取订购是 自动。仅为此同步模式不会发出其他CPU指令 某些编译器优化会受到影响......
首先是真的,x86遵循严格的内存排序? 似乎效率非常低,总是强加于此。意味着每次写入和读取都有围栏?
另外,如果我在x86系统上有一个对齐的int,原子变量是否可以用于任何目的?
答案 0 :(得分:11)
是的,x86确实有严格的内存排序,请参阅Intel manuals的第3A卷第8.2章。较老的x86处理器(如386)提供了真正严格的排序(称为强排序)语义,而更现代的x86处理器在少数情况下略微放松了条件,但您无需担心。例如,当写入是高速缓存命中时(因此读取的地址不同),Pentium和486允许读取高速缓存未命中超过缓冲写入。
是的,效率低下。有时,高性能软件仅针对具有更宽松内存排序要求的其他架构编写。
是的,原子变量仍然在x86上有用。它们与编译器具有特殊的语义,使得典型的read-modify-write操作以原子方式发生。如果你有两个线程同时递增一个原子变量(我的意思是C ++ 11中类型为std::atomic<T>
的变量),你可以放心,该值将大2;没有std::atomic
,你可能会得到错误的值,因为一个线程在执行增量时缓存了寄存器中的当前值,即使存储到内存在x86上是原子的。
答案 1 :(得分:4)
确实,在x86上,所有商店都有发布,所有加载都具有获取语义。
这不会也不应该影响您编写C ++的方式:要编写并发,无竞争的代码,您必须使用std::atomic
构造或锁。
架构细节的含义是,在x86上,只要您最多要求获取/发布顺序,就会对原子字大小类型的操作生成很少或没有额外代码。 (顺序一致性将发出mfence
指令。但是,您仍然必须使用C ++原子类型,并且不能省略它们以便拥有正确,格式正确的程序。原子变量的一个重要特性是它们阻止编译器重新排序,这对于程序的正确性至关重要。
(Pre-C ++ 11,您将不得不使用编译器提供的扩展,例如GCC的__sync_*
函数套件,这将使编译器正常运行。如果您真的想使用裸变量,你至少必须自己插入编译器障碍。)
答案 2 :(得分:1)
有一个不错的重新排序操作的表,可以发生,并且(例如)x86很少有。其他架构(众所周知的Alpha)几乎可以做任何事情。
对于内存模型由标准定义,x86等本质上是合规的。
关于原子变量的问题答案略有不同。对变量的任何修改都涉及竞争条件,这样当多个线程更新同一个变量时,更新可能会丢失。定义原子变量使得它们是原子操作的正确类型,从而消除了这种竞争条件。所以他们的目的之一不是订购。
答案 3 :(得分:0)
请注意,释放/获取语义不一定意味着每条指令后都有一个mfence。在x86上保持,可以在@Adam Rosenfield引用的手册中看到,或者快速查看Wikipedia。然而,x86具有存储的释放语义并且可以获取负载的语义。
来自Kerrek SB的答案:
架构细节的含义是,在x86上,只要您最多要求获取/发布顺序,就会对原子字大小类型的操作生成很少或没有额外代码。 (但顺序一致性会发出mfence指令。)
请注意,顺序一致性是默认值! (例如参见cppreference).
这意味着......
#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
以解释x86上的上述放松()。
然而......
#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上的g ++ -std = c ++ 11 -S -O3)
...不会插入mfence,因为x86具有存储的释放语义并且可以获取加载的语义。