内存屏障使用的通常模式与您在关键部分的实现中所使用的模式相匹配,但为生产者和消费者分成对。例如,您的关键部分实现通常采用以下形式:
而 (!pShared->lock.testAndSet_Acquire()) ;
//(这个循环应该包括所有正常的临界区的东西,比如
// 旋转,浪费,
// pause() 指令,以及对资源的last-resort-give-up-and-blocking
// 直到锁可用。)
// 访问共享内存。
pShared->foo = 1
v = pShared-> goo
pShared->lock.clear_Release()
上面的获取内存屏障确保在成功修改锁之前可能已经启动的任何加载(pShared->goo)都被丢弃,必要时重新启动。
释放内存屏障确保在清除保护共享内存的锁字之前完成从 goo 到(本地说)变量 v 的加载。
您在典型的生产者和消费者原子标志场景中有类似的模式(您的样本很难判断这是否是您正在做的事情,但应该说明这个想法)。
假设您的生产者使用了一个原子变量来指示其他一些状态已准备好使用。你会想要这样的东西:
pShared->goo = 14
pShared->atomic.setBit_Release()
如果在生产者中没有“写入”屏障,则无法保证硬件不会在 goo 存储通过 cpu 存储队列并向上通过可见的内存层次结构之前到达原子存储(即使你有一种机制可以确保编译器按照你想要的方式排序)。
在消费者
if ( pShared->atomic.compareAndSwap_Acquire(1,1) )
{
v = pShared->goo
}
如果没有“读取”屏障,您将不会知道在原子访问完成之前硬件还没有为您获取粘性。原子(即:使用互锁函数操作的内存,执行诸如 lock cmpxchg 之类的操作)仅相对于自身而言是“原子的”,而不是其他内存。
现在,必须提到的剩下的事情是屏障构造是高度不可移植的。您的编译器可能为大多数原子操作方法提供了 _acquire 和 _release 变体,这些是您可以使用它们的各种方式。根据您使用的平台(即:ia32),这些很可能正是没有 _acquire() 或 _release() 后缀的情况。这很重要的平台是 ia64(实际上已经死了,除了在 HP 上仍然轻微抽搐)和 powerpc。ia64 在大多数加载和存储指令(包括像 cmpxchg 这样的原子指令)上都有 .acq 和 .rel 指令修饰符。powerpc 对此有单独的说明(isync 和 lwsync 分别为您提供读写障碍)。
现在。说了这么多。你真的有充分的理由走这条路吗?正确地做这一切可能非常困难。为代码审查中的许多自我怀疑和不安全感做好准备,并确保您有大量的高并发测试,以及各种随机时序场景。除非您有充分的理由避免使用关键部分,否则不要自己编写该关键部分。