无锁编程与内存栅栏

时间:2022-05-14 12:09:29

一个压箱底的问题是 levelDB 中的原子指针的实现:没有使用到锁,而是使用到了一个内存栅栏,以下是源码:


原子指针是什么?这个概念可能听得多,但用意何在估计很少有人能说得上来!这里我用白话去解释一下:

指针的赋值并非是一个原子操作:如 cpu0 正要执行 void *p = k;若 k 不在 cpu0 的缓存中,或者 k 在 cpu0 缓存中,但是他收到了 k 的 invalidtion 消息,不能直接从缓存读 k,要去其它 cpu 缓存中取或者访存(耗费大量指令周期)。依此看来,一条简单的赋值语句,绝非一个原子操作这么简单。另外地,指针的位数与机器字长一致,可能无法在一个时钟周期内完成赋值,如果有多个线程同时对一个指针赋值,则可能会使得指针的值处于中间状态,对这个指针的解引用是末定义的行为!

举例一下应用场景:

CPU0 执行:

p = 0x0001920;

assigned=true;

CPU1 执行:

while(!assinged);

int v = *((int*)p);

很有可能出现对 0 地址取值的崩溃行为!原因在于,无法保证当 assigned=true 时,一定有 p = 0x0001920;

根据另外一篇文章《cpu 乱序执行与问题》的解释,解决办法是:

CPU0 执行:

assigned=true;

sfence();

p = 0x0001920;

CPU1 执行:

while(!assinged);

lfence();

int v = *((int*)p);

而如果把这一思想封装到一个类里面,就是这样:


inline void* Acquire_Load() const

 {
     void* result = rep_;//待返回的旧值:正是调用 Acquire_Load 那一时刻的值

     MemoryBarrier();//处理Invalidate Queues,加载新值。
     return result;
  }


inline void Release_Store(void* v) {
     MemoryBarrier();//release,将 CPU cache 刷新到 store buffer。相当于 _WriteBarrier//顺序写

     rep_ = v;
  }


注意上面的两个函数都是 inline 的:如果不是 inline ,则不需要使用 MemoryBarrier(),原因在于,在 C/C++ 中,函数就具有 memory barrier 的作用。