一个压箱底的问题是 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 的作用。