MESI 协议可以自动同步 CPU 内核之间的变量值吗?

问题描述

据我所知,对变量的并发访问需要某种同步(互斥、原子、内存屏障...),否则无论尝试多少次,在一个线程中读取都可能永远无法获得更新值。

但是,我的同事说 MESI 协议(不考虑没有 MESI 或类似东西的 cpu)能够在 cpu 缓存之间自动同步,如果读取由其他线程更新的变量,而没有任何读写同步(只是普通读取,例如“if(a != 0)”),经过一段时间后,如果继续尝试,读取最终将获得更新的值。我认为这里没有保证。

所以我写了一个代码来测试:

volatile int * volatile a = 0; // avoid compiler reorder
void set() {
    a = new int(1);
    std::cout << "set complete" << std::endl;
}
void read(int i) {
    while(1) {
        if(a != 0) {
            std::cout << i << " detected" << std::endl;
            break;
        }
    }
}
int main()
{
    std::thread td00(std::bind(read,0));
    std::thread td01(std::bind(read,1));
    std::thread td02(std::bind(read,2));
    std::thread td03(std::bind(read,3));
    std::thread td04(std::bind(read,4));
    // wait a moment to make sure 'set' gets called after 'read' runs
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    std::thread td1(set);
    td1.join();
    td00.detach();
    td01.detach();
    td02.detach();
    td03.detach();
    td04.detach();
    std::this_thread::sleep_for(std::chrono::minutes(60));
    return 0;
}

但是,运行会受到多种因素的影响,有时会阻塞,有时会打印“检测”。这不能成为有力的证明。

我已经搜索过这个,但文档对此不清楚。似乎 MESI 确实可以进行“自动同步”(程序员无需做任何事情),“PrRd”和“PrWr”似乎只是没有 LOCK 或 CMPXCHG 或类似内容的正常读写请求。但是为了加速,它引入了一个存储缓冲区,这会使cpu混乱并使“自动同步”的效果失效。为了修复这种混乱,程序员需要使用工具(内存屏障)来控制它。这意味着程序员必须手动进行同步才能使事情正确。

我的理解正确吗?如果是,假设程序员不手动执行,是否有时间延迟来保证获取更新值?我认为读取可能永远不会获得更新的值,但是我找不到证据。

解决方法

结论是:x86_64 是缓存一致的,普通的简单写入对共享一个总线的所有其他内核或 CPU 全局可见。

然而,这对于编写普通的应用程序代码是没有用的(不包括编译器、操作系统内核等低级别的东西......)。语言记忆模型对编码器完全隐藏了那些缓存一致性协议。编码不应该依赖或利用那些协议特性,因为编译器或语言虚拟机,运行时会混乱,优化你的代码。即使你知道到底会发生什么,不遵守语言记忆模型来编写代码仍然是微妙且容易出错的。

其中一种可能性是,即使在 set func 调用(A reference 以显示这是如何发生的)之前,也会使有问题的示例代码打印“x检测”,或者变量 a 是如果没有 volatile 关键字,存储在寄存器中会使 mesi 无能为力。更不用说大多数语言都没有 c/c++ 比较 volatile 关键字,这允许编码器选择只编译器不“更改”原始代码。