class SkipList
class SkipList 是Level DB中的重要数据结构,存储在memtable中的数据通过SkipList来存储和检索数据,它有优秀的读写性能,且和红黑树相比,更适合多线程的操作。
SkipList
SkipList还是一个比较简单的数据结构,它首先是一个List(链表),读写操作也和List相差不大。SkipList的复杂之处是每一个Node有一个高度的信息,带有这个高度信息的Node,可以看成一个Node Array [Height],其中的Height小于或等于SkipList 的 Max Height,如图1所示。
图1. Max Height = 4 's SkipList
当我们需要往这个SkipList里面添加一个Node的时候,这个新的Node他有不同的概率得到Height,如图2所示,key = 7 的 node,它有probability(概率)= p ,height = 1,有probability(概率)= (1 - p) * p, height = 2,有probability(概率)= (1 - p)* (1 - p) * p, height = 3,最后,它有probability(概率)= 1 - other probability,height = 4。
图2. Max Height = 4 's SkipList insert key = 7
Level DB 中的实现
Level DB中实现了class SkipList,下面来梳理总结一下这个SkipList的一些特点。
乐观锁
Level DB中实现了SkipList,这里面很有意思的是在实现上为了保证多线程的一致性使用了原子操作和乐观锁。
乐观锁相比于悲观锁的优势:
1.避免加锁/解锁的系统调用;
2.避免线程调度时的上下文切换的开销;
由于在Level DB中的SkipList的读写操作都有,读主要是get操作,写主要是insert操作,注意这里面没有delete操作,在读多的场景使用悲观锁显然不划算,同时写操作,这里面添加元素属于短时的操作(即使乐观锁 CAS --- Compare-And-Swap 也不会自旋很久),所以在Level DB的这个场景下使用乐观锁是高效和合理的。
//乐观锁 - 读
Node* Next(int n) {
assert(n >= 0);
// Use an 'acquire load' so that we observe a fully initialized
// version of the returned Node.
return next_[n].load(std::memory_order_acquire);
}
//乐观锁 - 写
void SetNext(int n, Node* x) {
assert(n >= 0);
// Use a 'release store' so that anybody who reads through this
// pointer observes a fully initialized version of the inserted node.
next_[n].store(x, std::memory_order_release);
}