CAS 无锁队列

       队列是常用的数据结构,采用的FIFO(first in firstout)原则,新元素(等待进入队列的元素)总是被插入到尾部,而读取的时候总是从头部开始读取。在计算中队列一般用来做排队(如线程池的等待排队,锁的等待排队),用来做解耦(生产者消费者模式),异步等等。在java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列。队列在现实生活中也很常见,例如去超市买东西排队付钱,先来的先付账走人回家,后来的要等前面的人付完钱才能付账。

       首先我们看一段队列代码:

       

复制代码
type Queue struct {
    head  unsafe.Pointer
    tail     unsafe.Pointer
    Reset  func(interface{})
    New    func() interface{}
}

// one node in queue
type Node struct {
    val  interface{}
    next unsafe.Pointer
}

func QueueNew()(*Queue){
    queue := new(Queue)
    queue.head =  unsafe.Pointer(new(Node))
    queue.tail = queue.head
    return queue
}

func (self *Queue) EnQueue(val interface{}) {

    if self.Reset!= nil{
        self.Reset(val)
    }
    newNode := unsafe.Pointer(&Node{val: val, next: nil})
    var tail, next unsafe.Pointer
    tail = self.tail
    ((*Node)(tail)).next = newNode
    self.tail = newNode
}

func (self *Queue) DeQueue() (val interface{}) {
    var head, tail, next unsafe.Pointer
    head = self.head
    tail = self.tail
    next = ((*Node)(head)).next
    if head == tail {
        // There's no data in the queue.
        return nil
    }
    val = ((*Node)(next)).val
    self.head = next
    return val
}
复制代码

       这是一般的队列实现方法,适用于单线程但如果是多线程操作就麻烦了。例如在超市柜台结账,大家都按规则进行排队没有问题,但是如果有两个人张大妈和李大妈都着急结账回家接孙子,同时跑到了同一个队列的队尾,她们两都说自己应该排在队尾。那么问题就来了。那么对于多线程操作同一个队列,可以用锁的方法来实现,在入队和出队前加上锁即可:

复制代码
type Queue struct {
    sync.RWMutex
    head unsafe.Pointer
    tail unsafe.Pointer
    Reset func(interface{})
    New func() interface{}
}

func (self *Queue) EnQueue(val interface{}) {
    self.Lock()
    defer self.Unlock()

    if self.Reset != nil {
        self.Reset(val)
    }
    newNode := unsafe.Pointer(&Node{val: val, next: nil})
    var tail, next unsafe.Pointer
    tail = self.tail
    ((*Node)(tail)).next = newNode
    self.tail = newNode
}

func (self *Queue) DeQueue() (val interface{}) {
    var head, tail, next unsafe.Pointer

    self.Lock()
    defer self.Unlock()

    head = self.head
    tail = self.tail
    next = ((*Node)(head)).next
    if head == tail {
        // There's no data in the queue.
        return nil
    }
    val = ((*Node)(next)).val
    self.head = next
    return val
}
复制代码

         但是,这种加锁的方法在多进程的操作中会消耗很多系统资源,使用不当还会造成死锁,下面推荐一种CAS的方法来实现队列的安全出队和入队。CAS(Compare and Swap),比较并交换,在大多数处理器架构,CAS的具体是判断一个内存上的数据是否是所判断的值,如果是,那么执行修改;如果不是,那么将不做操作并返回当前值。CAS是一种乐观锁,多线程执行过程中,多个线程去修改内存中的数据,有且只有一个能修改成功,但是失败的线程不会中断或者挂起。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func (self *Queue) EnQueue(val interface{}) {
 
    if self.Reset!= nil{
        self.Reset(val)
    }
    newNode := unsafe.Pointer(&Node{val: val, next: nil})
    var tail, next unsafe.Pointer
    for {
        tail = self.tail
        next = ((*Node)(tail)).next
        if tail != self.tail{
            runtime.Gosched()
            continue
        }<br>                //[PositionA]-----------A new node may already enqueue-------------
        if next != nil {
            atomic.CompareAndSwapPointer(&(self.tail), tail, next)
            continue
        }
        if atomic.CompareAndSwapPointer(&((*Node)(tail).next), nil,newNode ) {
            break
        }
        runtime.Gosched()
    }
    atomic.CompareAndSwapPointer(&(self.tail),tail, newNode)
}
 
func (self *Queue) DeQueue() (val interface{}) {
    var head, tail, next unsafe.Pointer
    for {
        head = self.head
        tail = self.tail
        next = ((*Node)(head)).next
        if head != self.head{
            runtime.Gosched()
            continue
        }
        if next == nil{
            if self.New != nil{
                return self.New()
            }else{
                return nil
            }
 
        }
        if head == tail {
            atomic.CompareAndSwapPointer(&(self.tail), tail, next)
        }else{
            val = ((*Node)(next)).val<br>                        //[PositionB]---------The head node may already Dequeue---------
            if atomic.CompareAndSwapPointer(&(self.head), head, next) {
                return val
                }
        }
        runtime.Gosched()
    }
}

  多线程在运行这段代码的过程中可能在位置A和位置B发生抢占,所以要先进行比较,如果一样再进行操作,这样就能保证一致性。

posted @   JaneySJ  阅读(1557)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示