在我评论的代码库中,我发现了以下习语.
void notify(struct actor_t act) {
write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
global.data = data;
notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
case 'M': use_data(global.data);break;
...
}
Run Code Online (Sandbox Code Playgroud)
"抓住它",我对作者说,我的团队的一名高级成员,"这里没有内存障碍!你不能保证global.data将从缓存刷新到主内存.如果线程A和线程B将运行两个不同的处理器 - 这个方案可能会失败".
高级程序员咧嘴一笑,慢慢解释,仿佛在解释他五岁的男孩如何系鞋带:"听小男孩,我们在这里看到很多线程相关的错误,在高负载测试中,在真实客户中",他他停下来留下他长长的胡须,"但我们从来没有这个成语的错误".
"但是,它在书中说......"
"安静!",他立刻叫醒我,"也许理论上,它不能保证,但实际上,你使用函数调用的事实实际上是一个内存屏障.编译器不会重新排序指令global.data = data,因为它无法知道是否任何人在函数调用中使用它,并且x86架构将确保其他CPU在线程B从管道读取命令时将看到这段全局数据.请放心,我们有充足的现实问题需要担心.我们不需要在虚假的理论问题上投入额外的努力.
"请放心,我的孩子,你会理解将真正的问题与我需要获得博士的非问题分开."
他是对的吗?这在实践中真的不是问题(比如x86,x64和ARM)吗?
这是我所学到的一切,但他确实有一个长胡子和一个非常聪明的外观!
额外的积分如果你能告诉我一段代码证明他错了!
某些语言提供的volatile修饰符被描述为在读取支持变量的内存之前执行"读取内存屏障".
读取存储器屏障通常被描述为一种方法,用于确保CPU在屏障之后执行读取之前执行读取之前所请求的读取.但是,使用此定义,似乎仍然可以读取过时值.换句话说,以特定顺序执行读取似乎并不意味着必须查询主存储器或其他CPU以确保读取的后续值实际上反映了读取屏障时系统中的最新值或随后写入阅读障碍.
因此,volatile是否真的保证读取最新值或者只是(喘气!)读取的值至少与屏障之前的读取一样是最新的?还是其他一些解释?这个答案有什么实际意义?
在"C#4 in a Nutshell"中,作者表明这个类有时可以写0 MemoryBarrier但是我无法在我的Core2Duo中重现:
public class Foo
{
int _answer;
bool _complete;
public void A()
{
_answer = 123;
//Thread.MemoryBarrier(); // Barrier 1
_complete = true;
//Thread.MemoryBarrier(); // Barrier 2
}
public void B()
{
//Thread.MemoryBarrier(); // Barrier 3
if (_complete)
{
//Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine(_answer);
}
}
}
private static void ThreadInverteOrdemComandos()
{
Foo obj = new Foo();
Task.Factory.StartNew(obj.A);
Task.Factory.StartNew(obj.B);
Thread.Sleep(10);
}
Run Code Online (Sandbox Code Playgroud)
这种需要对我来说似乎很疯狂.如何识别出现这种情况的所有可能情况?我认为如果处理器改变了操作顺序,它需要保证行为不会改变.
你还懒得使用障碍吗?
通常在互联网上我发现LFENCE在处理器x86中没有任何意义,即它什么都不做,所以相反MFENCE我们可以绝对无痛地使用SFENCE,因为MFENCE= SFENCE+ LFENCE= SFENCE+ NOP= SFENCE.
但是如果LFENCE没有意义,那么为什么我们有四种方法在x86/x86_64中建立顺序一致性:
LOAD(没有围栏)和STORE+MFENCELOAD (没有围栏)和 LOCK XCHGMFENCE+ LOAD和STORE(没有围栏)LOCK XADD(0)和STORE(没有围栏)取自这里:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
以及Herb Sutter在第34页底部的表演:https://skydrive.live.com/view.aspx?status = 4E86B0CF20EF15AD!24884&app = WordPdf&wdo = 2&authkey =!AMtj_EflYn2507c
如果LFENCE没有做任何事情,那么方法(3)将具有以下含义:SFENCE + LOAD and STORE (without fence)但是SFENCE之前没有任何意义LOAD.即如果LFENCE什么都不做,方法(3)没有意义.
它LFENCE在处理器x86/x86_64中是否有任何意义上的指令?
回答:
1. …
我和同事为在x86,x64,Itanium,PowerPC和其他10年历史的服务器CPU上运行的各种平台编写软件.
我们刚刚讨论了pthread_mutex_lock()... pthread_mutex_unlock()等互斥函数本身是否足够,或者受保护变量是否需要是volatile.
int foo::bar()
{
//...
//code which may or may not access _protected.
pthread_mutex_lock(m);
int ret = _protected;
pthread_mutex_unlock(m);
return ret;
}
Run Code Online (Sandbox Code Playgroud)
我担心的是缓存.编译器是否可以在堆栈或寄存器中放置_protected的副本,并在赋值中使用该陈旧值?如果没有,是什么阻止了这种情况发生?这种模式的变化是否易受攻击?
我假设编译器实际上并不理解pthread_mutex_lock()是一个特殊函数,所以我们只是受序列点保护吗?
非常感谢.
更新:好的,我可以看到一个趋势,答案解释了为什么不稳定是坏的.我尊重这些答案,但有关该主题的文章很容易在网上找到.我在网上找不到的,以及我问这个问题的原因,就是我如何保护我没有不稳定.如果上面的代码是正确的,那么缓存问题如何无懈可击?
Nutshell中的C#4(强烈推荐的btw)使用以下代码来演示MemoryBarrier的概念(假设A和B在不同的线程上运行):
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B(){
Thread.MemoryBarrier(); // Barrier 3;
if(_complete){
Thread.MemoryBarrier(); // Barrier 4;
Console.WriteLine(_answer);
}
}
}
Run Code Online (Sandbox Code Playgroud)
他们提到障碍1和4阻止这个例子写0和障碍2和3提供新鲜度保证:他们确保如果B在A之后运行,读_complete将评估为真.
我不是真的得到它.我想我明白为什么壁垒1和4是必要的:我们不想在写_answer进行优化,并放置在写后_complete(屏障1),我们需要确保_answer没有被缓存(光栅4) .我也认为我理解为什么Barrier 3是必要的:如果A在写完_complete = true之后才运行,B仍然需要刷新_complete以读取正确的值.
我不明白为什么我们需要障碍2!我的一部分说这是因为可能线程2(运行B)已经运行直到(但不包括)if(_complete),因此我们需要确保_complete被刷新.
但是,我不知道这有多大帮助.是不是仍然可以在A 中将_complete设置为true但是B方法会看到_complete的缓存(错误)版本?即,如果线程2运行方法B直到第一个MemoryBarrier之后,然后线程1运行方法A直到_complete = true但没有进一步,然后线程1恢复并测试是否(_complete) - 如果不导致错误 …
c# multithreading thread-safety shared-memory memory-barriers
当我正在阅读有关内存模型,障碍,排序,原子等等时,编译器栅栏的概念经常会出现,但通常情况下,它也会与CPU栅栏配对,正如人们所期望的那样.
但是,偶尔我会读到仅适用于编译器的fence构造.一个例子是C++ 11 std::atomic_signal_fence函数,它在cppreference.com上声明:
std :: atomic_signal_fence等效于std :: atomic_thread_fence,除了没有发出内存排序的CPU指令.仅按顺序指示抑制编译器对指令的重新排序.
我有五个与此主题相关的问题:
正如名称所暗示的那样std::atomic_signal_fence,是一个异步中断(例如一个被内核抢占以执行信号处理程序的线程)唯一一种只有编译器的栅栏才有用的情况?
它的用处是否适用于所有体系结构,包括强烈排序的体系结构x86?
是否可以提供一个特定的示例来演示仅编译器栅栏的用途?
使用时std::atomic_signal_fence,使用acq_rel和seq_cst订购之间有什么区别吗?(我希望它没有任何区别.)
这个问题可能是由第一个问题所覆盖,但我足够的好奇,一下也无妨具体问:是否曾经需要使用围栏与thread_local访问?(如果有的话,我希望只有编译器的围栏atomic_signal_fence才能成为首选工具.)
谢谢.
在阅读了更多的博客/文章等之后,我现在对内存屏障之前/之后的加载/存储行为感到困惑.
以下是Doug Lea在他关于JMM的一篇澄清文章中的两个引用,它们都非常简单:
对我来说,Doug Lea的澄清比另一个更严格:基本上,这意味着如果负载屏障和存储屏障位于不同的监视器上,则无法保证数据的一致性.但后者意味着即使屏障位于不同的监视器上,数据的一致性也会得到保证.我不确定我是否正确理解这两个,而且我不确定它们中的哪一个是正确的.
考虑以下代码:
public class MemoryBarrier {
volatile int i = 1, j = 2;
int x;
public void write() {
x = 14; //W01
i = 3; //W02
}
public void read1() {
if (i == 3) { //R11
if (x == 14) //R12
System.out.println("Foo");
else
System.out.println("Bar");
}
}
public void read2() {
if (j == 2) { //R21
if (x == 14) //R22
System.out.println("Foo");
else
System.out.println("Bar"); …Run Code Online (Sandbox Code Playgroud) 在C++中,原子能遭受虚假存储吗?
例如,假设m和n是原子能和m = 5最初.在主题1中,
m += 2;
Run Code Online (Sandbox Code Playgroud)
在线程2中,
n = m;
Run Code Online (Sandbox Code Playgroud)
结果:最终值n应为5或7,对吧?但它可能是虚假的6吗?它是虚假的4或8,甚至是其他什么?
换句话说,C++内存模型是否禁止线程1表现得好像这样做?
++m;
++m;
Run Code Online (Sandbox Code Playgroud)
或者,更奇怪的是,好像它做到了这一点?
tmp = m;
m = 4;
tmp += 2;
m = tmp;
Run Code Online (Sandbox Code Playgroud)
参考文献:H.-J.Boehm&SV Adve,2008,图1.(如果您点击链接,那么,在论文的第1部分中,查看第一个项目符号项:"......提供的非正式规范")
替代形式的问题
一个答案(赞赏)表明,上述问题可能会被误解.如果有帮助,那么这是另一种形式的问题.
假设程序员试图告诉线程1 跳过操作:
bool a = false;
if (a) m += 2;
Run Code Online (Sandbox Code Playgroud)
C++内存模型是否禁止线程1在运行时表现,就好像它这样做?
m += 2; // speculatively alter m
m -= 2; // oops, should not have altered! reverse the alteration
Run Code Online (Sandbox Code Playgroud)
我问,因为之前联系过的Boehm和Adve似乎解释说多线程执行可以
可编译的示例代码
如果您愿意,这里有一些您可以实际编译的代码.
#include …Run Code Online (Sandbox Code Playgroud)