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

Skiplist原理

skiplist示意圖
skiplist示意圖

skiplist的效率可以和平衡樹媲美,平均O(logN),最壞O(N)的查詢效率,但是用skiplist實(shí)現(xiàn)比平衡樹實(shí)現(xiàn)簡單,所以很多程序用跳躍鏈表來代替平衡樹。

內(nèi)存屏障

內(nèi)存屏障,也稱內(nèi)存柵欄,內(nèi)存柵障,屏障指令等,是一類同步屏障指令,是CPU或編譯器在對內(nèi)存隨機(jī)訪問的操作中的一個(gè)同步點(diǎn),使得此點(diǎn)之前的所有讀寫操作都執(zhí)行后才可以開始執(zhí)行此點(diǎn)之后的操作。更多細(xì)節(jié)

leveldb支持多線程操作,但skiplist中并沒有使用鎖,信號量來實(shí)現(xiàn)同步控制,因?yàn)殒i機(jī)制導(dǎo)致某個(gè)線程占有資源,其他線程阻塞的情況,導(dǎo)致系統(tǒng)資源利用率降低。所以leveldb采用的是內(nè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è)平臺不同。。我就沒有具體研究了,感興趣的朋友可以去自己搜搜資料。

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]; // 占位用
};

剛開始看到port::AtomicPointer next_[1];這句我整個(gè)人是懵逼的=。=,心想這在函數(shù)調(diào)用中不是特么越界么,后來發(fā)現(xiàn)大佬還是大佬,我還是too young too simple,看下面這個(gè)申請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]來占位,然后在申請內(nèi)存的時(shí)候,實(shí)際的數(shù)組大小和本節(jié)點(diǎn)的的height一致。

插入&&查詢

leveldb中為skiplist自身僅實(shí)現(xiàn)了insertcontain兩個(gè)公開的函數(shù)接口。
查詢邏輯相當(dāng)簡單,直接上代碼

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)插入

對于上述兩種情況,作者說明并不存在并發(fā)問題,為何呢?
不存在并發(fā)的關(guān)鍵在于最后的for循環(huán)中,由最下層向上插入可以保證當(dāng)前層一旦插入后,其下層狀態(tài)已經(jīng)更新。 SetNext為原子操作,保證讀線程在調(diào)用Next查找節(jié)點(diǎn)時(shí)不存在并發(fā)問題。
當(dāng)然,多個(gè)寫之間的并發(fā)skiplist時(shí)非線程安全的,在leveldbmemtable中采用了另外的技巧來處理寫并發(fā)問題。

同樣leveldb中沒有提供顯式的刪除節(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è)雙向迭代器,很簡單的實(shí)現(xiàn),不具體分析了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容