0

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

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

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

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混乱,并使“自动同步”的效果失效。为了解决这个问题,程序员需要使用工具(内存屏障)来控制它。这意味着程序员必须手动进行同步才能使事情正确。

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

4

1 回答 1

0

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

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

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

于 2021-05-12T10:13:18.833 回答