4

我对使用内存屏障/围栏进行编程相当陌生,我想知道我们如何保证设置写入在随后在其他 CPU 上运行的工作函数中可见。例如,考虑以下情况:

int setup, sheep;

void SetupSheep():    // Run once
    CPU 1: setup = 0;
    ... much later
    CPU 1: sheep = 9;
    CPU 1: std::atomic_thread_fence(std::memory_order_release);
    CPU 1: setup = 1;

之后(不是同时)运行很多次:

void ManipulateSheep():
    CPU 2: int mySetup = setup;
    CPU 2: std::atomic_thread_fence(std::memory_order_acquire);
    CPU 2: // Use sheep...

在 CPU 2 上,如果mySetup为 1,sheep则保证为 9——但我们如何保证它mySetup不是 0?

到目前为止,我能想到的只是在 CPU 2 上旋转等待直到setup1。但这似乎很丑陋,因为旋转等待只需要等待第一次ManipulateSheep()被调用。肯定有更好的方法吗?

请注意,未初始化代码还有一个对称问题:假设您正在编写一个无锁数据结构,该结构在其生命周期内分配内存。在析构函数中(假设所有线程都已完成调用方法),您想要释放所有内存,这意味着您需要运行析构函数的 CPU 具有最新的变量值。在这种情况下甚至不可能旋转等待,因为析构函数无法知道“最新”状态是什么来检查它。

编辑:我想我要问的是:有没有办法说“等待我的所有商店传播到其他 CPU”(用于初始化)和“等待所有商店传播到我的 CPU”(用于未初始化)?

4

2 回答 2

1

事实证明,这#StoreLoad正是这种情况的正确障碍。正如 Jeff Preshing 简单解释的那样

StoreLoad屏障确保在屏障之前执行的所有存储对其他处理器可见,并且在屏障之后执行的所有加载都接收到屏障时可见的最新值。

在 C++11 中,std::atomic_thread_fence(std::memory_order_seq_cst)显然充当#StoreLoad屏障(以及其他三个:#StoreStore#LoadLoad#LoadStore)。请参阅此 C++11 草案文件

旁注:在 x86 上,该mfence指令充当#StoreLoad; _mm_fence()如果需要,这通常可以与编译器内在一起发出。

因此,无锁代码的模式可能是:

Initialize:
    CPU 1: setupStuff();
    CPU 1: std::atomic_thread_fence(std::memory_order_seq_cst);

Run parallel stuff

Uninitialize:
    CPU 2: std::atomic_thread_fence(std::memory_order_seq_cst);
    CPU 2: teardownStuff();
于 2012-11-05T03:24:40.587 回答
0

事实上,内存屏障并没有给你任何方法来等待条件变为真。您几乎肯定希望使用操作系统提供的功能来执行此操作,例如 pthread 条件变量,或较低级别的原语,例如 Linux 的 futex 调用。

但是,您在示例中显示的障碍至少足以确保ManipulateSheep可以判断绵羊是否已准备好。

(咩。)

于 2012-11-03T05:30:08.293 回答