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

本文深入探讨了Folly库中的ConcurrentSkipList,重点分析了在多线程环境中,其无锁查找、加锁修改的实现细节。通过sample usage展示了查找、插入和删除操作的流程,包括findNodeDownRight方法的优化,addOrGetData函数中的findInsertionPointGetMaxLayer、SkipListRandomHeight的height和sizeLimit计算,以及lockNodesForChange和删除操作涉及的Recycler机制。

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

 

主要的用法如下:

Sample usage:

 

     typedef ConcurrentSkipList<int>SkipListT;

     shared_ptr<SkipListT>sl(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<value_type, NodeType> 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::pair<NodeType*, int>findNodeDownRight(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<iterator,bool> insert(const key_type &data)

  1. 调用skip list的addOrGetData(data)方法
  2. 我们来看下addOrGetData的实现
std::pair<NodeType*,size_t> 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 <nodeHeight; ++layer) {

        newNode->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=" <<height()

        << " nodeheight="<< nodeHeight;

      succ = succs[layer];

     if (pred != prevPred) { // 可能连续多层的前继指针都是一个节点,这里可以避免多次上锁

        guards[layer] =pred->acquireGuard();

        prevPred = pred;

      }

       // 对于remove来说只要判断前继没有被删除并且前继的后继是后继节点即可

      valid = !pred->markedForRemoval()&&

        pred->skip(layer) == succ;  // check again after locking

        

       // 对于adding来说还需要判断后继节点没有被删除

      if (adding) {  // when adding a node, the succ shouldn't begoing away

        valid = valid && (succ ==nullptr || !succ->markedForRemoval());

      }

    }

 

    return valid;

  }
d.最后看看下growHeight(int     height)方法,主要逻辑是替换head节点

  void growHeight(int height) {

    NodeType* oldHead =head_.load(std::memory_order_consume);

    if (oldHead->height() >= height){  // someone else already did this

      return;

    }

    // 生成新的head节点,height参数就是在原来的heigth基础上加1

    NodeType* newHead =NodeType::create(height, value_type(), true);

 

    { // need to guard the head node in caseothers are adding/removing

      // nodes linked to the head.

      ScopedLocker g =oldHead->acquireGuard();

     newHead->promoteFrom(oldHead); // 从oldHead中把数据拷贝过来,类似拷贝构造函数

      NodeType* expected = oldHead;

       // 原子替换head_指针指向newHead

      if(!head_.compare_exchange_strong(expected, newHead,

          std::memory_order_release)) {

        // if someone has already done theswap, just return.

        NodeType::destroy(newHead);

        return;

      }

      oldHead->setMarkedForRemoval();

    }

    // 加入gc,具体流程在

    recycle(oldHead);

  }

 

删除:

bool remove(constkey_type &data)

  1. 实际调用的是skip list的remove方法
  2. 我们来分析一下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方法时才会去释放这些节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值