c++ 无锁队列

什么是CAS呢?Compare-and-Swap,即比较并替换,也有叫做Compare-and-Set的,比较并设置。

1、比较:读取到了一个值A,在将其更新为B之前,检查原值是否仍为A(未被其他线程改动)。

2、设置:如果是,将A更新为B,结束。[1]如果不是,则什么都不做。

上面的两步操作是原子性的,可以简单地理解为瞬间完成,在CPU看来就是一步操作。

有了CAS,就可以实现一个乐观锁。

class Queue
{
    
    
private:
  struct qnode<ElemType> *volatile _head = NULL;  // 随着pop后指向的位置是不一样的, head不是固定的
  struct qnode<ElemType> *volatile _tail = NULL;

public:
  Queue()
  {
    
    
    _head = _tail = new qnode<ElemType>;
    _head->_next = NULL;
    _tail->_next = NULL;
//    printf("Queue _head:%p\n", _head);
  }
//    printf("Queue _head:%p\n", _head);
  }
  void push(const ElemType &e)
  {
    
    
    struct qnode<ElemType> *p = new qnode<ElemType>;
    // printf("push head:%p, p:%p\n", _head, p);
    p->_next = NULL;
    p->_data = e;

    struct qnode<ElemType> *t = _tail;
    struct qnode<ElemType> *old_t = _tail;
    int count = 0;
    do
    {
    
    
      while (t->_next != NULL) // 非空的时候要去更新 t->_next
        t = t->_next;          // 找到最后的节点
      if (count++ >= 1)
      {
    
    
//        printf("push count:%d, t->_next:%p\n", count, t->_next);
      }
      // 将null换为p即是插入的节点
    } while (!__sync_bool_compare_and_swap(&t->_next, NULL, p));

    // 将最后的节点_tail更换为p节点
    __sync_bool_compare_and_swap(&_tail, old_t, p);
  }
  bool pop(ElemType &e)
  {
    
    
    struct qnode<ElemType> *p = NULL;
    struct qnode<ElemType> *np = NULL;
    int count = 0;
    do
    {
    
    
      p = _head; // 头节点,不真正存储数据
//      np =  _head->_next;
      if (p->_next == NULL) // 首元节点为空,则返回
      {
    
    
//          printf("return false");
        return false;
      }
    } while (!__sync_bool_compare_and_swap(&_head, p, p->_next));
    e = p->_data;
    // printf("pop p:%p\n", p);
//    delete p; // 因为我们已经将头部节点换成了p->_next, 所以可以释放掉
    return true;
  }

在push函数中需要两个操作,一个是改变tail一个是改变tail->next,所以需要两个__sync_bool_compare_and_swap。__sync_bool_compare_and_swap(&_tail, old_t, p)不用放在while循环里是因为,这里不需8要保证每个线程tail的值正确,因为可以通过不断的t = t->_next找到插入的位置。
在pop函数中,cas操作的只能是head而不能是head->next, 因为此时pop函数应该改成

  bool pop(ElemType &e)
  {
    
    
    struct qnode<ElemType> *p = NULL;
    struct qnode<ElemType> *np = NULL;
    int count = 0;
    do
    {
    
    
      np =  _head->_next;
      if (np == NULL) // 首元节点为空,则返回
      {
    
    
//          printf("return false");
        return false;
      }
    } while (!__sync_bool_compare_and_swap(&(_head->_next), np, np->_next));
    // printf("pop p:%p\n", p);
//    delete p; // 因为我们已经将头部节点换成了p->_next, 所以可以释放掉
    return true;
  }

而这时不能能保证np->_next一定存在:,如下图所示,A地址在线程1中为头结点,而在线程2中,A地址经过delete之后可能分配给一个新的节点,并且这个节点的next为null,所以此时线程1中np=A->next=NULL,cas中的np->next报错

在这里插入图片描述

按顺序插入

while (prev->next != NULL && prev->next->item < node->item)保证了pre一定是node的前继节点。

void insert(Node *prev, Node *node) {
    
    
    while (true) {
    
    
        while (prev->next != NULL && prev->next->item < node->item) {
    
    
            prev = prev->next;
        }
        node->next = prev->next;
        if (__sync_compare_and_swap(&prev->next, node->next, node)) {
    
    
            return;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_39849839/article/details/110005135