高性能服务系列【十】无锁队列

无锁结构的复杂比较高,即使像双向队列那样的简单数据结构,也很难控制,更别说树形结构。而无锁结构本身就是为了提高性能,减少多线程互锁切换,导致的性能损失。因此,基于固定长度数组实现的无锁队列最为流行。下面就以基于固定长度数组的环形队列为例,讲解无锁队列算法。

单读单写的环形队列,很早之前就在通讯行业中广泛应用。单读写的环形队列天然不需要锁,即使原子操作也不需要。经过精心设计的系统,可以充分利用这个特性,可以作为性能基准。

多写单读的无锁队列应用场景更多,使用起来更符合编程习惯。单写多读的无锁队列适用于负荷均衡的场景,这时候只能保持局部有序,需要特别注意。而多写多读的无锁队列,已经完全失去了序列,是否还有必要采用队列模式,就需要仔细权衡。

不论读写,只要有一个方向采用多线程,算法都是一样的。我们专门讲解这个算法。环形队列如何操作,比较简单,就不在这里赘述。

首先,我们先讲解下自旋锁的原理,多线程无锁的原理和自旋锁是类似的。以银行排队为例,到银行的时候,先从前台拿到一个号码,然后坐在大厅的座位上,等着叫号。这时候,办理业务的顺序和号码的顺序是一致的。

自旋锁的原理和银行排队类似,某个线程进入自旋锁的时候,先拿到一个序号,然后不断检查已经执行结束的序号是不是轮到自己了。这里就涉及到两个序号,一个序号是本线程拿到的序号,另外一个序号是已经执行结束的序号。

自旋锁强调线程之间的公平性,执行顺序严格按照拿到的序号大小。如果其中一个线程被调度出执行序列,那么后续线程就必须等待这个线程执行结束。这样就导致后续排队线程都要延后执行,整体延迟就上升,不可控制。

无锁队列多线程的原理和自旋锁的原理类似。以多线程写为例,同样包含两个原子序号,一个原子是负责分配序号,称之为sequencer原子,另外一个原子记录已经完成的最小序号,称之为gating原子。不同的是,加入一个状态数组,大小和环形队列一致。

每个线程首先拿到一个序号,不需要等待其他线程写入环形数组完成,自己完成写入后,设置状态数组为完成状态。这时候,多线程之间不需要互斥。相对于自旋锁,该线程操作完之后,需要从gating原子记录序列号开始,扫描到sequencer原子记录的序列号。如果这个范围的状态数组,都已经标记为完成状态,则重置gating原子。

在这个算法下,多线程写之间需要通过sequencer原子分配序号,以及在重置gating原子序号时,需要原子操作确保互斥外,其他都不需要锁来确保线程安全。多线程读也是相同原理的。

因为数据实际是存储在环形队列上,存在数组下标回绕情况,因此在分配序号时,还需要确保分配的序号,在环形队列上不会溢出,导致读写之间,在数组上互相覆盖。这时候,就要额外根据另外一个方向的gating序列号,判断本方向的sequencer能够分配的序列号,两者之间的差不会超过数组长度。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值