说到C++的并发内存模型,Stroustrup的C++编程语言,第4版,第1节.41.2.1,说:
...(像大多数现代硬件一样)机器无法加载或存储任何小于单词的东西.
但是,我的x86处理器,几年前,可以存储小于一个字的对象.例如:
#include <iostream>
int main()
{
char a = 5;
char b = 25;
a = b;
std::cout << int(a) << "\n";
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果没有优化,GCC将其编译为:
[...]
movb $5, -1(%rbp) # a = 5, one byte
movb $25, -2(%rbp) # b = 25, one byte
movzbl -2(%rbp), %eax # load b, one byte, not extending the sign
movb %al, -1(%rbp) # a = b, one byte
[...]
Run Code Online (Sandbox Code Playgroud)
评论是由我提出的,但是汇编是由GCC提出的.当然,它运行良好.
显然,我不明白Stroustrup在谈到硬件可以加载和存储任何小于一个单词的内容时所说的内容.据我所知,我的计划什么也不做,但加载和存储对象小于一个字的.
C++对零成本,硬件友好的抽象的彻底关注使C++与其他易于掌握的编程语言区别开来.因此,如果Stroustrup在公交车上有一个有趣的信号心理模型,或者有其他类似的东西,那么我想了解Stroustrup的模型.
什么是 Stroustrup谈论,拜托?
更长时间的背景声明 …
所以我正在阅读作为即将推出的C++ 0x标准一部分的内存模型.但是,我对允许编译器做什么的一些限制有点困惑,特别是关于推测性加载和存储的限制.
首先,一些相关的东西:
Hans Boehm关于线程和C++ 0x中的内存模型的页面
Sutter,"Prism:基于原理的Microsoft本地代码平台的顺序存储模型",N2197
现在,基本思想本质上是"数据无竞赛程序的顺序一致性",这似乎是编程简易性和允许编译器和硬件优化机会之间的妥协.如果不对不同线程对相同存储器位置的两次访问进行排序,则至少有一个存储到存储器位置,并且它们中的至少一个不是同步动作,则定义数据争用.这意味着对共享数据的所有读/写访问必须通过某些同步机制,例如互斥体或对原子变量的操作(嗯,可以对原子变量进行操作,只为专家提供放松的内存排序,但默认提供为顺序一致).
鉴于此,我对普通共享变量上的虚假或推测性加载/存储的限制感到困惑.例如,在N2338中我们有例子
switch (y) {
case 0: x = 17; w = 1; break;
case 1: x = 17; w = 3; break;
case 2: w = 9; break;
case 3: x = 17; w = 1; break;
case 4: x = 17; w = 3; break;
case 5: x = 17; w = 9; break;
default: x = 17; w = 42; break;
}
Run Code Online (Sandbox Code Playgroud)
不允许编译器转换为
tmp = …Run Code Online (Sandbox Code Playgroud) python线程是否像Java那样暴露了内存可见性和语句重新排序的问题?由于我找不到任何对"Python内存模型"或类似内容的引用,尽管很多人都在编写多线程Python代码,但我猜这些问题在这里并不存在.例如,没有volatile关键字.但似乎没有明确说明任何地方,例如,一个线程中的变量的更改立即对所有其他线程可见.
也许这些东西对于Python程序员来说都是非常明显的,但作为一个可怕的Java程序员,我需要一点额外的保证:)
我正在编写一些无锁代码,我想出了一个有趣的模式,但我不确定它是否会在轻松的内存排序下表现得如预期.
解释它的最简单方法是使用一个例子:
std::atomic<int> a, b, c;
auto a_local = a.load(std::memory_order_relaxed);
auto b_local = b.load(std::memory_order_relaxed);
if (a_local < b_local) {
auto c_local = c.fetch_add(1, std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
请注意,所有操作都使用std::memory_order_relaxed.
显然,在这上,负载为执行的线程a,并b在之前必须完成if情况进行评估.
类似地,读取 - 修改 - 写入(RMW)操作c必须在评估条件之后完成(因为它以条件为条件).
我想知道的是,这段代码保证的价值c_local至少高达最新的值a_local和b_local?如果是这样,如果放宽内存排序,这怎么可能?控制依赖是否与RWM操作一起充当某种获取范围?(请注意,在任何地方都没有相应的版本.)
如果上述情况属实,我相信这个例子也应该有效(假设没有溢出) - 我是对的吗?
std::atomic<int> a(0), b(0);
// Thread 1
while (true) {
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
if (a_local >= 0) { // Always true at runtime
b.fetch_add(1, std::memory_order_relaxed);
}
} …Run Code Online (Sandbox Code Playgroud) 在试图了解SubmissionPublisher(Java SE 10 中的源代码,OpenJDK | docs),在版本 9 中添加到 Java SE 的新类是如何实现的,我偶然发现了一些VarHandle我以前不知道的API 调用:
fullFence,acquireFence,releaseFence,loadLoadFence和storeStoreFence。
在做了一些研究之后,特别是关于内存屏障/栅栏的概念(我以前听说过它们,是的;但从未使用过它们,因此对它们的语义非常不熟悉),我想我对它们的用途有了基本的了解. 尽管如此,由于我的问题可能源于误解,我想确保我首先做对了:
内存屏障是关于读写操作的重新排序约束。
内存屏障可以分为两大类:单向和双向内存屏障,这取决于它们是否对读取或写入或两者都设置了约束。
C++ 支持多种内存屏障,但是,这些与VarHandle. 然而,一些在可用内存壁垒VarHandle提供排序的影响是兼容其相应的C ++内存屏障。
#fullFence 兼容 atomic_thread_fence(memory_order_seq_cst)#acquireFence 兼容 atomic_thread_fence(memory_order_acquire)#releaseFence 兼容 atomic_thread_fence(memory_order_release) #loadLoadFence并且#storeStoreFence没有兼容的 C++ 计数器部分兼容这个词在这里似乎非常重要,因为在细节方面语义明显不同。例如,所有 C++ 屏障都是双向的,而 Java 的屏障不是(必然)。
我的假设正确吗?如果是这样,我产生的问题是:
可用的内存屏障是否VarHandle会导致任何类型的内存同步? …
java concurrency memory-model java-memory-model memory-barriers
考虑下面的C++ 11片段.对于GCC和clang,这会编译为两个(顺序一致的)foo.C++内存模型是否允许编译器将这两个加载合并到一个加载中并对x和y使用相同的值?
我认为它不能合并这些负载,因为这意味着轮询原子不再起作用,但我找不到内存模型文档中的相关部分.
#include <atomic>
#include <cstdio>
std::atomic<int> foo;
int main(int argc, char **argv)
{
int x = foo;
int y = foo;
printf("%d %d\n", x, y);
return 0;
}
Run Code Online (Sandbox Code Playgroud) c++ memory-model compiler-optimization language-lawyer stdatomic
我的理解std::memory_order_acquire和std::memory_order_release如下:
获取意味着获取围栏之后出现的内存访问不能重新排序到围栏之前.
释放意味着在释放围栏之前出现的内存访问不能在围栏之后重新排序.
我不明白为什么特别是对于C++ 11原子库,获取围栏与加载操作相关联,而释放围栏与存储操作相关联.
为了澄清,C++ 11 <atomic>库允许您以两种方式指定内存屏障:要么可以将fence指定为原子操作的额外参数,例如:
x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
或者您可以std::memory_order_relaxed单独使用和指定围栅,例如:
x.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
我不明白的是,鉴于上述获取和发布的定义,为什么C++ 11特别将获取与加载相关联,并与商店一起发布?是的,我已经看过许多示例,这些示例显示了如何使用获取/加载与发布/存储来在线程之间进行同步,但一般来说似乎是获取fences(防止语句后的内存重新排序)和发布的想法fences(在语句之前防止内存重新排序)与加载和存储的想法正交.
那么,为什么,例如,编译器不会让我说:
x.store(10, std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
我意识到我可以通过使用memory_order_relaxed,然后单独的atomic_thread_fence(memory_order_acquire)声明来完成上述操作,但同样,为什么我不能直接使用存储memory_order_acquire?
一个可能的用例可能是,如果我想确保某些存储,比如在执行可能影响其他线程的其他语句之前x = 10发生.
我目前正在阅读Anthony Williams的C++ Concurrency in Action.他的一个列表显示了这段代码,他声明z != 0可以解雇的断言.
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x()
{
x.store(true,std::memory_order_release);
}
void write_y()
{
y.store(true,std::memory_order_release);
}
void read_x_then_y()
{
while(!x.load(std::memory_order_acquire));
if(y.load(std::memory_order_acquire))
++z;
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire));
if(x.load(std::memory_order_acquire))
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load()!=0);
}
Run Code Online (Sandbox Code Playgroud)
所以我能想到的不同执行路径是这样的:
1)
Run Code Online (Sandbox Code Playgroud)Thread a (x is now true) Thread c (fails to increment z) Thread b (y …
我正在阅读Joe Duffy关于Volatile读取和写入以及及时性的帖子,我正在尝试理解帖子中最后一个代码示例:
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
…
Run Code Online (Sandbox Code Playgroud)
当执行第二CMPXCHG操作,它使用一个内存屏障,以保证价值m_state确实写入的最新值?或者它只是使用已存储在处理器缓存中的某些值?(假设m_state未声明为volatile).
如果我理解正确,如果CMPXCHG不会使用内存屏障,那么整个锁获取过程将不公平,因为第一个获取锁的线程很可能是将获得所有锁的线程.以下锁.我是否理解正确,或者我错过了什么?
编辑:主要问题实际上,在尝试读取m_state的值之前,调用CompareExchange是否会导致内存屏障.因此,当尝试再次调用CompareExchange时,是否所有线程都可以看到赋值0.
我已经看到了::std::thread等等的文章::std::forward,但我没有看过任何好文章::std::atomic.当然,还有标准提案文件,但我还没有看到任何想要使用该设施的人的良好文档.
有没有?我在哪里可以找到它?
memory-model ×10
c++ ×6
c++11 ×4
concurrency ×3
atomic ×2
stdatomic ×2
assembly ×1
c# ×1
java ×1
optimization ×1
python ×1
volatile ×1
x86 ×1