我对使用内存屏障/栅栏编程很新,我想知道如何保证设置写入在随后在其他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“(用于未初始化)?
答案 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)。