内存障碍:工作线程如何确保初始化写入?

时间:2012-11-03 05:09:28

标签: c++ memory-barriers memory-fences

我对使用内存屏障/栅栏编程很新,我想知道如何保证设置写入在随后在其他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上旋转等待直到setup为1.但是这看起来非常难看,因为旋转等待只需要等待第一次{{1 }} 被称为。当然必须有更好的方法吗?

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

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

2 个答案:

答案 0 :(得分:1)

事实证明#StoreLoad正是这种情况的正确障碍。 As explained simply by Jeff Preshing

  

StoreLoad屏障确保在屏障之前执行的所有商店对其他处理器可见,并且屏障之后执行的所有加载都会获得屏障时可见的最新值。

在C ++ 11中,std::atomic_thread_fence(std::memory_order_seq_cst)显然充当#StoreLoad障碍(以及其他三个:#StoreStore#LoadLoad#LoadStore )。请参阅this C++11 draft paper

旁注:在x86上,mfence instruction充当#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();

答案 1 :(得分:0)

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

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

(BAA)。