folly源码分析(4)- ConcurrentSkipList.h

这里主要实现了多线程环境下的skiplist,读操作(count, find, skipper)都是lock free的,写操作(remove, add)也只是小范围的加了锁

 

主要的用法如下:

Sample usage:

 

     typedef ConcurrentSkipListSkipListT;

     shared_ptrsl(SkipListT::createInstance(init_head_height);

     {

       // It's usually good practice to hold anaccessor only during

       // its necessary life cycle (but not ina tight loop as

       // Accessor creation incurs ref-countingoverhead).

       //

       // Holding it longer delaysgarbage-collecting the deleted

       // nodes in the list.

         // Accessor提供了访问skip list的接口,我们不能直接使用skip list对象来访问数据

       SkipListT::Accessor accessor(sl);

       accessor.insert(23); // 增加节点

       accessor.erase(2);   // 删除节点

       for (auto &elem : accessor) {

         // use elem to access data

       }

       ... ...

     }

 

还有一种访问方式是Skipper,主要是用来跳过一部分数据,例如

      {

       SkipListT::Accessor accessor(sl);

       SkipListT::Skipper skipper(accessor);

       skipper.to(30); // 跳到比30大的第一个节点

       if (skipper) {

         CHECK_LE(30, *skipper);

       }

       ... ...

       // GC may happen when the accessor getsdestructed.

     }


 

 

我们这里重点从Accessor来分析一下查找、增加和删除的流程:

查找:

typedef detail::csl_iterator iterator; // 利用boostiterator_facade生成的iterator

iterator find(const key_type &value);

  1. 调用skip list的find方法
  2. 调用findNode方法,如果找到节点并且该节点没有markedForRemoval的话就返回,否则返回nullptr
  3. 调用findNodeDownRight(查找时先向下遍历,然后再向右遍历)方法,这里值得说一下的是,skip list里还实现了一个findNodeRightDown(查找时先向右遍历,然后再向下遍历)方法,但性能不如findNodeDownRight,因为后者的cache locality要更好一些
  4. 我们来看下findNodeDownRight方法是怎么实现的:
 std::pairfindNodeDownRight(const value_type &data) const {

   NodeType *pred = head_.load(std::memory_order_consume); // 从head开始查找

    int ht = pred->height();

    NodeType *node = nullptr;

 

    bool found = false;

    while (!found) {

     // stepping down,直到找到一个节点pred的数据比data大

      for (; ht > 0 && less(data,pred->skip(ht - 1)); --ht) {}

      if (ht == 0) returnstd::make_pair(pred->skip(0), 0);  //not found

 

      node = pred->skip(--ht);  // node <= data now

     // stepping right,继续接近data

      while (greater(data, node)) {

        pred = node;

        node = node->skip(ht);

      }

      found = !less(data, node);

    }

    return std::make_pair(node, found);

  }

 

增加:

std::pair insert(const key_type &data)

  1. 调用skip list的addOrGetData(data)方法
  2. 我们来看下addOrGetData的实现
std::pair addOrGetData(const value_type &data) {

    NodeType *preds[MAX_HEIGHT],*succs[MAX_HEIGHT];

    NodeType *newNode;

    size_t newSize;

    while (true) {

      int max_layer = 0;

       // 找到data对应的节点,以及它的前继和后继,max_layer返回当前skip list的最大层级

       // 返回值layer是data对应的节点备找到时的layer

      int layer =findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);

 

     if (layer >= 0) {// 如果找到

        NodeType *nodeFound = succs[layer];

        DCHECK(nodeFound != nullptr);

        if (nodeFound->markedForRemoval()) {

          continue;  // if it's getting deleted retry findingnode.

        }

       // wait until fully linked. 可能节点被其他线程加入了,暂时还没有fully linked

         // 等待完成后再返回给用户完整的节点

        while(UNLIKELY(!nodeFound->fullyLinked())) {}

        return std::make_pair(nodeFound, 0);

      }

 

      // need to capped at the original height-- the real height may have grown

      // 按概率生成新的节点高度,新节点的高度上限设为max_layer+1

      // 值得注意的是选取概率是1/e

      int nodeHeight =detail::SkipListRandomHeight::instance()->

        getHeight(max_layer + 1);

 

      ScopedLocker guards[MAX_HEIGHT];

       // 把前继全部加上锁

      if (!lockNodesForChange(nodeHeight,guards, preds, succs)) {

        continue; // give up the locks andretry until all valid

      }

 

      // locks acquired and all valid, need tomodify the links under the locks.

       // 按照生成的高度建立新的节点

      newNode = NodeType::create(nodeHeight,data);

       // 把新的节点联入skip list中

      for (int layer = 0; layer setSkip(layer,succs[layer]);

        preds[layer]->setSkip(layer,newNode);

      }

        

       // 标记fully linked

      newNode->setFullyLinked();

      newSize = incrementSize(1);

      break;

    }

 

    int hgt = height();

    size_t sizeLimit =

     detail::SkipListRandomHeight::instance()->getSizeLimit(hgt);

      

     // 检查是否需要增加skip list节点的高度

    if (hgt < MAX_HEIGHT && newSize> sizeLimit) {

      growHeight(hgt + 1);

    }

    CHECK_GT(newSize, 0);

    return std::make_pair(newNode, newSize);

  }


 

  1. 我们再分别来看这个函数中调用的几个方法
    1.  
    2. 先来看findInsertionPointGetMaxLayer 
      1.   
      2. 它首先把skip       list的高度返回为max_layer 
       
      1.   
      2. 然后按照right       down的方式查找节点,不同的是在查找过程中会保留前继指针preds[]和后继指针succs[] 
    1.  
    2. 再来看看SkipListRandomHeight::getHeight方法和SkipListRandomHeight::getSizeLimit方法 
      1.   
      2. 在SkipListRandomHeight构造的时候会初始化两张表  
        •    
        • lookupTable_:高度的概率表,高度1的概率是1-e,高度2的概率是(1-e)*e,高度3的概率是(1-e)*e*e以此类推  
          
        •    
        • sizeLimitTable_:skip list的高度对应的最大的list size,高度1的size为1,高度2的size为1*e,高度3的size为1*e*e,以此类推  
         
       
      1.   
      2. 回到getHeight方法,这里用随机函数生成一个0~1之间的double值p,然后在lookupTable中找比p大的值对应的表索引i,找到后获得的高度就是i+1 
       
      1.   
      2. getSizeLimit方法也类似,以参数height为sizeLimitTable的索引,返回对应高度的sizeLimit 
    1.  
    2. 接下来看看lockNodesForChange方法,我们来看下代码
boollockNodesForChange(int nodeHeight,

      ScopedLocker guards[MAX_HEIGHT],

     NodeType *preds[MAX_HEIGHT], // 插入或删除节点的前继

     NodeType *succs[MAX_HEIGHT], // 插入或删除节点的后继

     bool adding=true) {// adding为true表明该函数是在add里备调用,否则是在remove里被调用

    NodeType *pred, *succ, *prevPred = nullptr;

    bool valid = true;

    for (int layer = 0; valid && layer< nodeHeight; ++layer) {

      pred = preds[layer];

      DCHECK(pred != nullptr) <<"layer=" << layer << " height=" < 
   
  • 实际调用的是skip list的remove方法
  • 我们来分析一下remove的源码
  •  

    bool remove(const value_type &data) {
    
        NodeType *nodeToDelete = nullptr;
    
        ScopedLocker nodeGuard;
    
       bool isMarked = false; //表示是否已经完成对删除节点的标记(markForRemoval)
    
        int nodeHeight = 0;
    
        NodeType* preds[MAX_HEIGHT],*succs[MAX_HEIGHT];
    
     
    
        while (true) {
    
          int max_layer = 0;
    
           // 先找到要删除的节点,以及它的前继和后继,max_layer返回当前skiplist的最大层级
    
           // 返回值layer是data对应的节点备找到时的layer
    
          int layer =findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);
    
          // okToDelete的判断条件是:节点fully linked,节点没有被标记markedForRemoval,
    
          // 并且节点的top layer和查到节点的layer一致,否则说明该节点是partailly linked
    
          if (!isMarked && (layer < 0 ||!okToDelete(succs[layer], layer))) {
    
            return false;
    
          }
    
           
    
           // 给要删除的节点设置markedForRemoval
    
          if (!isMarked) {
    
            nodeToDelete = succs[layer];
    
            nodeHeight = nodeToDelete->height();
    
            nodeGuard =nodeToDelete->acquireGuard();
    
            if(nodeToDelete->markedForRemoval()) return false;
    
            nodeToDelete->setMarkedForRemoval();
    
            isMarked = true;
    
          }
    
     
    
          // acquire pred locks from bottom layerup
    
          // 获取所有前继的锁
    
          ScopedLocker guards[MAX_HEIGHT];
    
          if (!lockNodesForChange(nodeHeight,guards, preds, succs, false)) {
    
            continue;  // this will unlock all the locks
    
          }
    
           
    
           // 修改前继指针,删除对应节点
    
          for (int layer = nodeHeight - 1; layer>= 0; --layer) {
    
            preds[layer]->setSkip(layer,nodeToDelete->skip(layer));
    
          }
    
     
    
         incrementSize(-1);// 这里不会降低高度
    
          break;
    
        }
    
        // 把节点加入gc中
    
        recycle(nodeToDelete);
    
        return true;
    
      }
    

     

    1. 这里值得提一下的是Recycler对象,他负责回收被删除的节点,但其实它只是把节点加入一个vector,然后在Recycler对象析构或者显示调用release方法时才会去释放这些节点

    你可能感兴趣的:(folly源码分析(4)- ConcurrentSkipList.h)