在我评论的代码库中,我发现了以下习语.
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)吗?
这是我所学到的一切,但他确实有一个长胡子和一个非常聪明的外观!
额外的积分如果你能告诉我一段代码证明他错了!
假设我有自己的非内联函数LockMutex和UnlockMutex,它们正在使用一些适当的互斥体 - 例如boost - inside.对于LockMutex和UnlockMutex的调用,编译器如何知道不重新排序其他操作?它不可能知道如何在其他编译单元中实现这些功能.
void SomeClass::store(int i)
{
LockMutex(_m);
_field = i; // could the compiler move this around?
UnlockMutex(_m);
}
Run Code Online (Sandbox Code Playgroud)
ps:应该使用类的实例来保存锁以保证解锁.我把它留下来简化示例.