leveldb源碼學(xué)習(xí)--skiplist

Skiplist原理

skiplist示意圖
skiplist示意圖

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)了insertcontain兩個(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;
  }
}

leveldbskiplist的核心操作當(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í)非線程安全的根蟹,在leveldbmemtable中采用了另外的技巧來(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),不具體分析了冰寻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末须教,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子性雄,更是在濱河造成了極大的恐慌没卸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秒旋,死亡現(xiàn)場(chǎng)離奇詭異约计,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)迁筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煤蚌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人细卧,你說(shuō)我怎么就攤上這事尉桩。” “怎么了贪庙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蜘犁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我止邮,道長(zhǎng)这橙,這世上最難降的妖魔是什么奏窑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮屈扎,結(jié)果婚禮上埃唯,老公的妹妹穿的比我還像新娘。我一直安慰自己鹰晨,他們只是感情好墨叛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著模蜡,像睡著了一般漠趁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哩牍,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天棚潦,我揣著相機(jī)與錄音,去河邊找鬼膝昆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叠必,可吹牛的內(nèi)容都是我干的荚孵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纬朝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼收叶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起共苛,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤判没,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后隅茎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體澄峰,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年辟犀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俏竞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堂竟,死狀恐怖魂毁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情出嘹,我是刑警寧澤席楚,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站税稼,受9級(jí)特大地震影響烦秩,放射性物質(zhì)發(fā)生泄漏垮斯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一闻镶、第九天 我趴在偏房一處隱蔽的房頂上張望甚脉。 院中可真熱鬧,春花似錦铆农、人聲如沸牺氨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猴凹。三九已至,卻和暖如春岭皂,著一層夾襖步出監(jiān)牢的瞬間郊霎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工爷绘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留书劝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓土至,卻偏偏與公主長(zhǎng)得像购对,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陶因,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容