Skiplist原理
![skiplist示意圖](http://7xjnip.com1.z0.glb.clouddn.com/luodw--skiplist.jpg)
skiplist
的效率可以和平衡樹(shù)媲美先鱼,平均O(logN)
瓜客,最壞O(N)
的查詢效率适瓦,但是用skiplist
實(shí)現(xiàn)比平衡樹(shù)實(shí)現(xiàn)簡(jiǎn)單竿开,所以很多程序用跳躍鏈表來(lái)代替平衡樹(shù)。
內(nèi)存屏障
內(nèi)存屏障玻熙,也稱內(nèi)存柵欄否彩,內(nèi)存柵障,屏障指令等揭芍,是一類同步屏障指令胳搞,是CPU或編譯器在對(duì)內(nèi)存隨機(jī)訪問(wèn)的操作中的一個(gè)同步點(diǎn),使得此點(diǎn)之前的所有讀寫操作都執(zhí)行后才可以開(kāi)始執(zhí)行此點(diǎn)之后的操作称杨。更多細(xì)節(jié)
leveldb
支持多線程操作,但skiplist
中并沒(méi)有使用鎖筷转,信號(hào)量來(lái)實(shí)現(xiàn)同步控制姑原,因?yàn)殒i機(jī)制導(dǎo)致某個(gè)線程占有資源,其他線程阻塞的情況呜舒,導(dǎo)致系統(tǒng)資源利用率降低锭汛。所以leveldb
采用的是內(nèi)存屏障來(lái)實(shí)現(xiàn)同步機(jī)制。
class AtomicPointer {
private:
void* rep_;
public:
AtomicPointer() { }
explicit AtomicPointer(void* p) : rep_(p) {}
inline void* NoBarrier_Load() const { return rep_; }
inline void NoBarrier_Store(void* v) { rep_ = v; }
inline void* Acquire_Load() const {
void* result = rep_;
MemoryBarrier();//內(nèi)存屏障
return result;
}
inline void Release_Store(void* v) {
MemoryBarrier();
rep_ = v;
}
};
關(guān)于內(nèi)存屏障的實(shí)現(xiàn)袭蝗,各個(gè)平臺(tái)不同唤殴。。我就沒(méi)有具體研究了到腥,感興趣的朋友可以去自己搜搜資料朵逝。
Node
template<typename Key, class Comparator>
struct SkipList<Key,Comparator>::Node {
explicit Node(const Key& k) : key(k) { }
Key const key;
// Accessors/mutators for links. Wrapped in methods so we can
// add the appropriate barriers as necessary.
Node* Next(int n) {
assert(n >= 0);
// Use an 'acquire load' so that we observe a fully initialized
// version of the returned Node.
return reinterpret_cast<Node*>(next_[n].Acquire_Load());
}
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].Release_Store(x);
}
// No-barrier variants that can be safely used in a few locations.
Node* NoBarrier_Next(int n) {
assert(n >= 0);
return reinterpret_cast<Node*>(next_[n].NoBarrier_Load());
}
void NoBarrier_SetNext(int n, Node* x) {
assert(n >= 0);
next_[n].NoBarrier_Store(x);
}
private:
// Array of length equal to the node height. next_[0] is lowest level link.
port::AtomicPointer next_[1]; // 占位用
};
剛開(kāi)始看到port::AtomicPointer next_[1];
這句我整個(gè)人是懵逼的=。=乡范,心想這在函數(shù)調(diào)用中不是特么越界么配名,后來(lái)發(fā)現(xiàn)大佬還是大佬,我還是too young too simple晋辆,看下面這個(gè)申請(qǐng)Node
的函數(shù)
template<typename Key, class Comparator>
typename SkipList<Key,Comparator>::Node*
SkipList<Key,Comparator>::NewNode(const Key& key, int height) {
char* mem = arena_->AllocateAligned(
sizeof(Node) + sizeof(port::AtomicPointer) * (height - 1));
return new (mem) Node(key);
}
這樣就可以理解了渠脉,實(shí)際上這是一個(gè)技巧性的作法,利用port::AtomicPointer next_[1]
來(lái)占位瓶佳,然后在申請(qǐng)內(nèi)存的時(shí)候芋膘,實(shí)際的數(shù)組大小和本節(jié)點(diǎn)的的height一致。
插入&&查詢
leveldb
中為skiplist
自身僅實(shí)現(xiàn)了insert
和contain
兩個(gè)公開(kāi)的函數(shù)接口霸饲。
查詢邏輯相當(dāng)簡(jiǎn)單为朋,直接上代碼
template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::Contains(const Key& key) const {
Node* x = FindGreaterOrEqual(key, NULL);
if (x != NULL && Equal(key, x->key)) {
return true;
} else {
return false;
}
}
leveldb
中skiplist
的核心操作當(dāng)屬insert
先上代碼然后再具體分析
template<typename Key, class Comparator>
void SkipList<Key,Comparator>::Insert(const Key& key) {
Node* prev[kMaxHeight];
Node* x = FindGreaterOrEqual(key, prev);
// Our data structure does not allow duplicate insertion
assert(x == NULL || !Equal(key, x->key));
int height = RandomHeight();
if (height > GetMaxHeight()) {
for (int i = GetMaxHeight(); i < height; i++) {
prev[i] = head_;
}
max_height_.NoBarrier_Store(reinterpret_cast<void*>(height));
}
x = NewNode(key, height);
for (int i = 0; i < height; i++) {
// NoBarrier_SetNext() suffices since we will add a barrier when
// we publish a pointer to "x" in prev[i].
x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));
prev[i]->SetNext(i, x);
}
}
思想是,在插入高度為height
的節(jié)點(diǎn)時(shí)贴彼,首先要找到這個(gè)節(jié)點(diǎn)height
個(gè)前節(jié)點(diǎn)潜腻,然后插入就和普通的鏈表插入一樣。
max_height更新的兩種情況:
1. 讀到舊的max_height_器仗,而后寫線程更新了max_height_并正在進(jìn)行或完成節(jié)點(diǎn)插入
2. 讀到新的max_height_融涣,而寫線程正在進(jìn)行或完成節(jié)點(diǎn)插入
對(duì)于上述兩種情況童番,作者說(shuō)明并不存在并發(fā)問(wèn)題,為何呢威鹿?
不存在并發(fā)的關(guān)鍵在于最后的for
循環(huán)中剃斧,由最下層向上插入可以保證當(dāng)前層一旦插入后,其下層狀態(tài)已經(jīng)更新忽你。 SetNext為原子操作幼东,保證讀線程在調(diào)用Next查找節(jié)點(diǎn)時(shí)不存在并發(fā)問(wèn)題。
當(dāng)然科雳,多個(gè)寫之間的并發(fā)skiplist
時(shí)非線程安全的根蟹,在leveldb
的memtable
中采用了另外的技巧來(lái)處理寫并發(fā)問(wèn)題。
同樣leveldb
中沒(méi)有提供顯式的刪除節(jié)點(diǎn)操作糟秘,但實(shí)際上是可以刪除的简逮,因?yàn)楫?dāng)我們插入數(shù)據(jù)時(shí),key
的形式為key:value
尿赚,當(dāng)刪除數(shù)據(jù)時(shí)散庶,則插入key:deleted
類似刪除的標(biāo)記,等到Compaction
再刪除凌净。
leveldb
中還自己封裝了一個(gè)雙向迭代器悲龟,很簡(jiǎn)單的實(shí)現(xiàn),不具體分析了冰寻。