zoukankan      html  css  js  c++  java
  • 无锁队列

    1 伪命题

    这本身是个伪命题。

    多线程之间使用队列是一定需要做到同步的。也就是说一定是需要同步手段的,一定要在一个线程读写的时候,阻塞另一个线程。既然不然用锁,那就是用原子操作吧。

    2 CAS

    在多线程中,最怕的就是当你修改一个变量,但是修改还没有结束,线程调度,控制权转交,恰好别的线程也在修改这个数据。

    当线程再次调度回来的时候,线程看到的值已经和之前的不一样了,但是他并不知道,因此仍然继续之前的修改,这样就会产生错误。

    因此原子操作的含义就是,要不操作全部完成要么一点都不开始。这和数据库的事务不同,因为原子操作没有回滚。

    也就是说原子操作,一旦开始某个操作,那么就必须要完全的完成。

    CAS原子操作——Compare & Set,或是 Compare & Swap,

    3 实现

    队列,这里使用链表来实现

    struct ListNode
    {
     ListNode *next;
     int val;     
     ListNode(int x):val(x),ListNode(nullptr){}
    };
    
    class UnlockQueue
    {
    public:
       void push(int x);
       int pop(); 
    private:
      ListNode *push_start;//
      ListNode *pop_start;
    };
    

      

    然后,考虑push,在push的时候,要在push_start的next上加节点,此时push_start->next是等于nullptr的。

    void UnlockQueue::push(int x)
    {
     ListNode *n=new ListNode(x);
     ListNode *tmp=push_start;
      
     while(cas(push_start->next,nullptr,n)!=true)
     {
       tmp=push_start;
     }
      push_start=n;
    }
    

      

    解释一下,创建要添加的节点以后,就用cas轮序push_start->next,当等于nullptr的时候,就将next设置为n。

    因为是个原子操作,因此同一时刻,就只有这个线程在设置next。同时,当别的线程设置了next以后,那么next就不在指向nullptr,那么就会一直循环。

    当设置完以后,就将push_start设置为n,此时其他线程,tmp指向改变,真正的指向尾部,因此其他线程就可在cas通过了。

    因此本质上还是锁。但是这里使用了轮询。

    原因在于,这个设置过程时间很短暂,所以没有必要使用锁。

    这里,类的两个字段,可以不用加上volatile关键字修饰,因为别的线程会改变自己正在使用的数据。因为在cas的实现上,这两个数据成员所处的内存一直是由某个cpu独享的。因此不会有其他核心去改变这个数据。

    看到这里估计也就明白了,这其实是一个自己实现的自旋锁,嗯,自旋锁,使用轮询的方式,不让进程阻塞。

    对应的pop操作,同理,也是这样的思想。

      

    4 CAS原理

  • 相关阅读:
    Jenkins安装
    Python操作yaml文件
    class 中构造函数与析构函数
    python发送邮件(yagmail模块)
    filter、map函数的区别
    python redis操作
    多个 python的pip版本选择
    python Excel操作
    python MD5操作
    缓存淘汰算法之LRU实现
  • 原文地址:https://www.cnblogs.com/perfy576/p/8593899.html
Copyright © 2011-2022 走看看