在我查看的代码库中,我发现了以下成语。
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;
...
}
“等一下”,我对作者,我团队的一位资深成员说,“这里没有内存屏障!你不保证global.data
会从缓存刷新到主内存。如果线程A和线程B会运行两个不同的处理器——这个方案可能会失败”。
高级程序员咧嘴一笑,慢慢解释,好像在解释他五岁的男孩如何系鞋带:“听着小男孩,我们在这里看到了很多线程相关的错误,在高负载测试中,在真实客户端中”,他停下来抓挠他长长的胡须,“但我们从来没有遇到过这个成语的错误”。
“可是,书上说……”
“安静!”他迅速让我安静下来,“也许理论上,不能保证,但实际上,你使用函数调用的事实实际上是一个内存屏障。编译器不会重新排序指令global.data = data
,因为它不知道是否任何在函数调用中使用它的人,x86 架构将确保在线程 B 从管道中读取命令时,其他 CPU 将看到这条全局数据。请放心,我们有很多现实世界的问题要担心。我们不需要在虚假的理论问题上投入额外的精力。
“放心,我的孩子,到时候你会明白将真正的问题与我需要获得博士学位的非问题区分开来。”
他是对的吗?这在实践中真的不是问题吗(比如 x86、x64 和 ARM)?
这与我学到的一切背道而驰,但他确实留着长胡子和非常聪明的外表!
如果你能告诉我一段代码证明他错了,加分!