最佳答案
在我查看的一个代码库中,我发现了以下习语。
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 在两个不同的处理器上运行——这个方案可能会失败”。
高级程序员咧嘴一笑,慢慢地解释,好像在向他五岁的儿子解释如何系鞋带: “听着,年轻人,我们在这里看到了许多与线程相关的 bug,在高负载测试中,以及在真正的客户端中,”他停下来抓了抓自己长长的胡子,“但是我们从来没有遇到过这个习惯用法的 bug。”。
“但是,书上说...”
“安静!”,他马上让我安静下来,“也许从理论上讲,这是不能保证的,但在实践中,你使用函数调用的事实实际上是一个记忆障碍。编译器不会重新排序指令 global.data = data
,因为它不知道是否有人在函数调用中使用它,而 x86体系结构将确保其他 CPU 在线程 B 从管道读取命令时看到这段全局数据。请放心,我们有足够的现实世界的问题要担心。我们不需要在虚假的理论问题上投入额外的精力。
“放心吧,我的孩子,假以时日,你就会明白,把真正的问题与我需要获得博士学位的无关问题区分开来。”
他是正确的吗? 在实践中(比如 x86、 x64和 ARM) ,这真的不是问题吗?
这和我学到的一切都不一样,但是他确实留着长胡子,而且长得很帅!
如果你能给我一段代码证明他是错的,那就加分!