0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

无锁队列的潜在优势

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-09 09:23 次阅读

无锁队列

先大致介绍一下无锁队列。无锁队列的根本是CAS函数——CompareAndSwap,即比较并交换,函数功能可以用C++函数来说明:

int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
return old_reg_val;
}

它将reg的值与oldval的值进行对比,若相同则将reg赋新值。注意以上操作是原子操作。大部分语言都有提供CAS支持,不过函数原型可能有些微的不同,许多语言(包括go)中CAS的返回值是标识是否赋值成功的bool值。

无锁队列则是以CAS来实现同步的一种队列,我的具体实现这里就不贴出来了,有点冗长,文末给出了源码地址。这里仅仅大致给出实现思路,网上关于无锁队列的资料很多,这里就不详细说了。

EnQueue(x) //进队列改良版
{
q = new record();
q->value = x;
q->next = NULL;

p = tail;
oldp = p
do {
while (p->next != NULL)
p = p->next;
} while( CAS(p.next, NULL, q) != TRUE); //如果没有把结点链在尾上,再试

CAS(tail, oldp, q); //置尾结点
}

DeQueue() //出队列
{
do{
p = head;
if (p->next == NULL){
return ERR_EMPTY_QUEUE;
}
while( CAS(head, p, p->next) != TRUE );
return p->next->value;
}

自旋锁

自旋锁是加锁失败时接着循环请求加锁,直到成功。它的特点是不会释放CPU,故也没有互斥锁那种内核态切换操作,但缺点也很明显,就是会一直占用CPU,理论上适用于临界区小、不需要长时间加锁的场景。 这里只贴锁的相关代码,队列的实现就不贴了:

// 自旋锁
type spinMutex struct {
mutex int32
}
const locked = 1
const unlocked = 0
func (spin *spinMutex) lock() {
for !atomic.CompareAndSwapInt32(&spin.mutex, unlocked, locked) {}
}
func (spin *spinMutex) unlock() {
atomic.SwapInt32(&spin.mutex, unlocked)
}

互斥锁

这个没什么好说的,用的golang自带的互斥锁sync.Mutex。

测试

下面将分2种场景进行测试:分别是高并发和低并发。高并发我用4个协程往队列中push数据,4个协程从队列中pop数据(虽然不是很高,但足以区分性能,就没测太高并发了,毕竟测一次等的太久也累);低并发不好模拟,于是我干脆极端点改为无并发——先顺序写,再顺序读。

无并发

大致测试代码结构如下(删减了不关键的语句):

t1 := time.Now()
for i := 1; i <= dataNum; i++ {
suc := queue.PushBack(i)
}
queue.Disable()

for {
val, enable := queue.PopFront()
if !enable {
break
}
}
fmt.Println("用时:", time.Since(t1))

为了方便对比,我特地还增加了不加锁的队列的测试结果。测试结果如下:(左侧为dataNum数据量)

图片

添加图片注释,不超过 140 字(可选)

可以看到数据量小的时候性能差别还不明显,甚至cas还有少许的优势。但数据量一大就很明显的看出自旋锁的效率会高一点,cas次之。不过它们差别都不大。

高并发

这里用4个生产者4个消费者共用一个队列来模拟高并发。测试代码结构如下:

func test() {
wgr := sync.WaitGroup{}
wgw := sync.WaitGroup{}
t1 := time.Now()
for i := 0; i < 4; i++ {
wgr.Add(1)
go reader(i*1000000, &wgr)
}
for i := 0; i < 4; i++ {
wgw.Add(1)
go writter(&wgw)
}
wgr.Wait()
queue.Disable()
wgw.Wait()
fmt.Println("用时:", time.Since(t1))
}
func reader(startNum int, wg *sync.WaitGroup) {
for i := 0; i < dataNum; i++ {
suc := queue.PushBack(startNum + i)
for !suc {
suc = queue.PushBack(startNum + i)
}
}
wg.Done()
}
func writter(wg *sync.WaitGroup) {
for {
r, enable := queue.PopFront()
if enable == false {
break
}
if r == defaultVal {
continue
}
}
wg.Done()
}

这种情况下就没法测试无锁队列了,数据都不完整(已验证)。测试结果如下,左侧为读/写协程数*dataNum数据量(下面读/写协程数为4指总共开了8个协程):

图片

添加图片注释,不超过 140 字(可选)

可以看到cas有巨大的性能优势,甚至达到了3到5倍的性能差距,说明这个思路还是可行的!(先开始被chan打击到了)反倒是自旋锁的性能最差,这个倒有些出乎我的意料,按照我的理解在这种频繁加锁解锁的情况下自旋锁的性能应该更好才对,若有知情人士望告知。

分析

为了对这几种锁的性能特点有更深入的分析,这里还补充了几组测试,分别用了不同的协程数和数据量进行补充测试:

图片

添加图片注释,不超过 140 字(可选)

可以很明显的看到一个趋势——随着并发度增加,自旋锁的性能急剧下降,由无并发时的与cas性能几乎一样到最后与cas将近7倍的效率差。而mutex和cas情况下,随着并发度增加,性能影响并不大,下面将前面的测试数据重新组织一下方便对比:

图片

添加图片注释,不超过 140 字(可选)

可以看到总数据量不变的情况下,并发协程数对mutex和cas的影响非常小,基本在波动范围以内。相较之下自旋锁就比较惨了。

总结

**根据上面的结果来说的话,当实际竞争特别小的时候,可以考虑用自旋锁;而并发大的时候,用无锁队列这种结构有很大潜在优势。**之所以说潜在的是因为我也仅仅是简单的实现某种结构,肯定有考虑不全的地方,我写这个无锁例子主要用于测试,也没打算用于实际场景中。但是我尽量保证了同样的代码结构下,最大化各个锁结构对性能的影响。总的来说,本文测试结果仅作参考,希望能有抛砖引玉的效果。

最后,再附上源码地址:https://github.com/HandsomeRosin/lockfree

更新:

针对自旋锁效率低下的问题我仔细想了想,应该是原子操作cas耗时的问题(毕竟在无并发情况下,cas和真正不加锁还是有很大的性能差距)。于是对自旋锁的代码进行了微调,减少了CAS的调用次数:(被注释掉的是原本的代码逻辑)

func (spin *spinMutex) lock() {
// for !atomic.CompareAndSwapInt32(&spin.mutex, unlocked, locked) {}
BEGINING:
for spin.mutex != unlocked {}
if !atomic.CompareAndSwapInt32(&spin.mutex, unlocked, locked) {
goto BEGINING
}
}

事实证明,这样做效率确实提高了约1/4,不过还是改变不了它的大趋势(与cas和mutex的性能差距依旧巨大),所以就没更新前面的测试数据了。

不过这也佐证了CAS也是比较耗时的一个操作,平时还是不能肆意使用。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6710

    浏览量

    88296
  • 源码
    +关注

    关注

    8

    文章

    625

    浏览量

    28964
  • 函数
    +关注

    关注

    3

    文章

    4234

    浏览量

    61961
  • CAS
    CAS
    +关注

    关注

    0

    文章

    34

    浏览量

    15160
收藏 人收藏

    评论

    相关推荐

    使用USB进行测量应用的优势潜在危险

    使用USB进行测量应用的优势潜在危险 引言:USB用于测试与测量应用的优势很多,但是在选择USB
    发表于 05-11 21:11 321次阅读

    《有》/《》/《签约》/《解锁》/《越狱》/《激活》专

    《有》/《》/《签约》/《解锁》/《越狱》/《激活》专业技术词解析 在讨论区里,大家看到:《有版》,《
    发表于 02-03 11:05 927次阅读

    AWorks软件设计,邮箱、消息队列和自旋使用方法

    本文介绍了邮箱、消息队列和自旋的使用方法。信号量只能用于任务间的同步,不能传递更多的信息,为此,AWorks提供了邮箱和消息队列服务,它们的主要区别在于支持的消息长度不同,在邮箱中,每条消息的长度固定为4字节,而在消息
    的头像 发表于 06-13 09:13 1.2w次阅读
    AWorks软件设计,邮箱、消息<b class='flag-5'>队列</b>和自旋<b class='flag-5'>锁</b>使用方法

    智能按键出现反应或禁止操作的原因坤坤智能告诉你

    智能按键出现反应或禁止操作的原因坤坤智能告诉你在日常生活中使用智能时,多多少少会遇到智能热键
    发表于 12-14 14:47 1w次阅读

    利用CAS技术实现队列

    【 导读 】:本文 主要讲解利用CAS技术实现队列。 关于队列的实现,网上有很多文章,虽
    的头像 发表于 01-11 10:52 2159次阅读
    利用CAS技术实现<b class='flag-5'>无</b><b class='flag-5'>锁</b><b class='flag-5'>队列</b>

    关于CAS等原子操作介绍 队列的链表实现方法

    在开始说队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作
    的头像 发表于 05-18 09:12 3207次阅读
    关于CAS等原子操作介绍 <b class='flag-5'>无</b><b class='flag-5'>锁</b><b class='flag-5'>队列</b>的链表实现方法

    怎么设计实现一个高并发的环形连续内存缓冲队列

    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头
    的头像 发表于 02-15 14:59 1109次阅读
    怎么设计实现一个<b class='flag-5'>无</b><b class='flag-5'>锁</b>高并发的环形连续内存缓冲<b class='flag-5'>队列</b>

    发烧友实测 | i.MX8MP 编译DPDK源码实现rte_ring队列进程间通信

    作者|donatello1996来源|电子发烧友题图|飞凌嵌入式rte_ring是一个用CAS实现的FIFO环形队列,支持多消费者/生产者同时出入队列,常用于多线程/多进程之间的通
    的头像 发表于 01-10 16:29 1717次阅读
    发烧友实测 | i.MX8MP 编译DPDK源码实现rte_ring<b class='flag-5'>无</b><b class='flag-5'>锁</b>环<b class='flag-5'>队列</b>进程间通信

    源智能的应用前景

    ,但应用前景广阔。源智能的发展优势:1.政策支持:近年来,国家大力推进物联网、大数据、新能源的发展,并且陆续出台各项产业政策,引导智能行业有序化、高端化发展,
    的头像 发表于 09-22 10:18 1299次阅读
    <b class='flag-5'>无</b>源智能<b class='flag-5'>锁</b>的应用前景

    新品上架——源智能把手

    为了迎合市场需求,2022年我司开始着手开发源智能把手。经过几个月的努力,2022年11月我司正式上架源智能把手源智能把手
    的头像 发表于 11-11 17:56 528次阅读
    新品上架——<b class='flag-5'>无</b>源智能把手<b class='flag-5'>锁</b>

    源智能系统之水务消防

    源智能系统之水务消防
    的头像 发表于 05-22 09:48 397次阅读
    <b class='flag-5'>无</b>源智能<b class='flag-5'>锁</b>系统之水务消防

    固态电池(SSBs)的潜在优势

    与用于日常手机和电动汽车的传统锂离子电池相比,固态电池(SSBs)具有重要的潜在优势
    发表于 09-25 09:28 471次阅读
    固态电池(SSBs)的<b class='flag-5'>潜在</b><b class='flag-5'>优势</b>

    如何实现一个多读多写的线程安全的队列

    在ZMQ队列的原理与实现一文中,我们已经知道了ypipe可以实现一线程写一线程读的队列
    的头像 发表于 11-08 15:25 856次阅读
    如何实现一个多读多写的线程安全的<b class='flag-5'>无</b><b class='flag-5'>锁</b><b class='flag-5'>队列</b>

    队列解决的问题

    为什么需要队列 队列解决了什么问题?
    的头像 发表于 11-10 15:33 739次阅读
    <b class='flag-5'>无</b><b class='flag-5'>锁</b><b class='flag-5'>队列</b>解决的问题

    CAS如何实现各种的数据结构

    ,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某⼀数据时由于执行顺序不确定性以及中断的不可预知性产⽣的数据不一致问题 有了CAS,我们就可以用它来实现各种(lock free)的数据结构 实现原理 该操作通过将内存中的值与指定数据进行比较,
    的头像 发表于 11-13 15:38 604次阅读
    <b class='flag-5'>无</b><b class='flag-5'>锁</b>CAS如何实现各种<b class='flag-5'>无</b><b class='flag-5'>锁</b>的数据结构