从Java 5开始,volatile关键字具有释放/获取语义,以使副作用对其他线程可见(包括对非易失性变量的赋值!).拿这两个变量,例如:
int i;
volatile int v;
Run Code Online (Sandbox Code Playgroud)
请注意,这i是一个常规的非易失性变量.想象一下线程1执行以下语句:
i = 42;
v = 0;
Run Code Online (Sandbox Code Playgroud)
在稍后的某个时间点,线程2执行以下语句:
int some_local_variable = v;
print(i);
Run Code Online (Sandbox Code Playgroud)
根据Java存储器模型,v线程1中的写入以及线程2中的读取v确保线程2看到i在线程1中执行的写入,因此打印值42.
我的问题是:volatile在C#中是否有相同的发布/获取语义?
制作类字段是否会在并发情况下volatile阻止所有内存可见性问题?是否有可能对于下面的类,获取Test对象引用的线程x首先看到0(默认值为int)然后是10?我认为这是可能的,当且仅当构造函数在没有完成(不正确的发布)的情况下Test放弃this引用.有人可以验证/纠正我吗?
class Test {
volatile int x = 10;
}
Run Code Online (Sandbox Code Playgroud)
第二个问题:如果是这样的话final int x=10;?
以下模式在许多软件中很常见,这些软件想告诉用户它做了多少事情:
int num_times_done_it; // global
void doit() {
++num_times_done_it;
// do something
}
void report_stats() {
printf("called doit %i times\n", num_times_done_it);
// and probably some other stuff too
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,如果多个线程可以在doit没有某种同步的情况下调用,则并发读取 - 修改 - 写入num_times_done_it可能是数据争用,因此整个程序的行为将是未定义的.此外,如果report_stats可以在doit没有任何同步的情况下同时调用,则在线程修改num_times_done_it和报告其值的线程之间存在另一个数据争用.
通常,程序员只想要doit尽可能少的开销来调用大多数正确的次数.
(如果你认为这个例子是微不足道的,Hogwild!比使用基本上这个技巧的数据无竞争随机梯度下降获得了显着的速度优势.而且,我相信Hotspot JVM正是这种无人看守,多线程访问共享计数器对于方法调用计数---虽然它是明确的,因为它生成汇编代码而不是C++ 11.)
明显的非解决方案:
volatile到组合,使数据的比赛好了,更换的声明num_times_done_it由volatile int num_times_done_it不能解决任何事情.report_stats,但这并不能解决doit和之间的数据竞争report_stats.此外,它很乱,它假设更新是关联的,并不真正适合Hogwild!的用法.是否有可能在一个非平凡的多线程C++ 11程序中实现具有良好定义语义的调用计数器,而无需某种形式的同步?
编辑:似乎我们可以使用memory_order_relaxed以下方式稍微间接地执行此操作:
atomic<int> num_times_done_it;
void doit() …Run Code Online (Sandbox Code Playgroud) 我们都知道堆栈和堆的想法,但我最近读到了第三个保存数据的选项:寄存器.
我很难找到关于这种类型的好文章,我发现的是:http://www.dotnetperls.com/method-parameter,以及C的很多内容,例如:http://igoro.com/存档/易失性-关键字在-C-存储器模型解释的/
到目前为止我唯一的实际信息:每个CPU都有自己的寄存器,可用于保存数据,以尽可能快的方式访问,例如在for循环中.
据我所见,这种注册是由CLR完成的.然后我想起了这个volatile-keyword,如果我们看一下MSDN:
volatile关键字表示某个字段可能被同时执行的多个线程修改.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
那么Volatile也是如此吗?它告诉CLR不要使用CPU寄存器而是堆栈/堆,它可以被所有CPU /线程访问?
我很抱歉这个令人困惑的问题,但关于这个话题的信息确实很少.
我的问题涉及std :: atomic以及该指针指向的数据.如果在线程1中我有
Object A;
std:atomic<Object*> ptr;
int bar = 2;
A.foo = 4; //foo is an int;
ptr.store(*A);
Run Code Online (Sandbox Code Playgroud)
如果在线程2中我发现ptr指向A,我可以保证ptr-> foo是4而bar是2吗?原子指针的默认内存模型(顺序一致)是否保证在原子存储之前发生的非原子(在这种情况下为A.foo)上的分配将在其看到同一atomic.store的赋值之前被其他线程看到对于这两种情况?
如果它有帮助或重要,我使用x64(我只关心这个平台),gcc(使用支持原子的版本).
宽松排序标记 std::memory_order_relaxed 的原子操作不是同步操作,它们不排序内存。它们只保证原子性和修改顺序的一致性。例如,x 和 y 最初为零,
// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B
// Thread 2:
r2 = x.load(memory_order_relaxed); // C
y.store(42, memory_order_relaxed); // D
Run Code Online (Sandbox Code Playgroud)
允许产生 r1 == r2 == 42 因为,虽然 A 在 B 之前被排序并且 C 在 D 之前被排序,但是没有什么可以阻止 D 在 y 的修改顺序中出现在 A 之前,并且 B 在修改中出现在 C 之前x 的顺序。
问题:是什么赋予上述代码属性A 在 B 之前排序并且C 在 D 之前排序?
编辑:
int A, B;
void foo()
{
A = …Run Code Online (Sandbox Code Playgroud) c++ multithreading memory-model stdatomic instruction-reordering
根据这个https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html,已发布的商店MOV在x86(包括x86-64)上实现为(进入内存).
根据他的http://en.cppreference.com/w/cpp/atomic/memory_order
memory_order_release:
具有此内存顺序的存储操作将执行释放操作:在此存储之后,不能对当前线程中的内存访问进行重新排序.这确保了当前线程中的所有写入在获取或相同原子变量的其他线程中可见,并且带有依赖关系到原子变量的写入在消耗相同原子的其他线程中变得可见.
我知道当使用memory_order_release时,之前完成的所有内存存储应该在此之前完成.
int a;
a = 10;
std::atomic<int> b;
b.store(50, std::memory_order_release); // i can be sure that 'a' is already 10, so processor can't reorder the stores to 'a' and 'b'
Run Code Online (Sandbox Code Playgroud)
问题:MOV对于这种行为,裸指令(没有明确的内存栅栏)是否足够?如何MOV告诉处理器完成以前的所有商店?
我正在研究这个网站:https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync,这对了解有关原子类的主题非常有帮助.
但这个关于放松模式的例子很难理解:
/*Thread 1:*/
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)
/*Thread 2*/
if (x.load (memory_order_relaxed) == 10)
{
assert (y.load(memory_order_relaxed) == 20) /* assert A */
y.store (10, memory_order_relaxed)
}
/*Thread 3*/
if (y.load (memory_order_relaxed) == 10)
assert (x.load(memory_order_relaxed) == 10) /* assert B */
Run Code Online (Sandbox Code Playgroud)
对我来说断言B应该永远不会失败,因为x必须是10且y = 10,因为线程2已经以此为条件.
但网站上说这个例子中的断言实际上可能是失败的.
C++ 支持原子线程栅栏,即保证使用std::atomic<>操作的线程的属性的栅栏,函数atomic_thread_fence. 它需要一个记忆顺序参数来调整围栏的“强度”。
我知道当并非所有原子操作都以“强”顺序完成时,围栏很有用:
(1) 包括 RMW 操作
所以所有这些(acquire、release 和 acq_rel 栅栏)的用处是显而易见的:它们允许使用比 acq/rel 弱的原子操作的线程(分别)正确同步。
但我不明白哪里memory_order_seq_cst特别需要作为围栏:
使用弱于memory_order_seq_cst原子操作和memory_order_seq_cst栅栏的含义是什么?
不能保证的memory_order_seq_cst栅栏会特别保证什么(就原子操作的可能排序而言)memory_order_acq_rel?