GoLang之[os浅尝]实现自旋锁

本文探讨了自旋锁的概念及其在Go语言中的实现,通过原子操作避免并发问题。文章展示了如何使用Go的atomic包创建自旋锁,并讨论了在自旋锁导致的CPU空耗问题。通过引入PAUSE指令进行优化,减少了资源浪费,实验结果显示性能显著提升。最后,提供了自旋锁的优化代码供读者参考。
摘要由CSDN通过智能技术生成

GoLang之[os浅尝]实现自旋锁

1.自旋锁

假设我们有一个变量初始值是0,有两个线程会持续不断的将n的数值增一,为避免出现并发问题,我们需要一把锁来保护变量n.
锁,本质上也是一个变量,我们可以将锁的初始值置为0,

在这里插入图片描述

若能够原子性的将锁的值由0置为1,就可以获得锁,然后操作变量n,

在这里插入图片描述

操作结束后,再原子性的将锁的值由1置为0,就可以释放锁.

在这里插入图片描述

但现在有两个线程都会尝试获得锁,而同一时刻只有一个能够成功,那另一个没有获得锁的线程该怎么办呢?直观的想法就是如果不成功,就一直尝试,直到能够获得锁为止,这就是"自旋锁"

2.Go语言实现一把自旋锁

先来构造应用场景,有两个协程,代号分别为0和1,它们都在循环中先获得锁,一个把变量n自增1,一个把变量自减1,然后释放锁,两个协程各自循环一亿次,最后打印变量n的值

在这里插入图片描述

再来看这个锁的实现,我们使用Go语言atomic提供的CompareAndSwapInt32函数实现锁变量的原子操作,并在获取锁失败时不断重复尝试

在这里插入图片描述

实际运行看看CPU的使用情况,在双核CPU的虚拟机上,使用率将近百分置二百,也就是说跑满了CPU,这是因为在双核环境下runtime会默认分配两个P,所以上述两个协程会在两个线程上并行执行,当一个协程持有锁的时候,另一个协程会持续高频的重复尝试获得锁,空耗CPU的情况就会比较严重,所以在工程应用中,通常不会只用简单的循环来实现自旋锁,

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

例如在nigin的自旋锁实现中,会在获取锁失败的情况下调用ngx_cpu_pause函数,而这个函数实际上会调用汇编上的pause指令,pause指令的主要作用一方面是防止内存乱序,另一方面是减少CPU的功耗,下面,我们也这样优化下看看效果,

在这里插入图片描述
在这里插入图片描述

我们找到Go语言中CompareAndSwapInt32函数的底层实现,真正实现在runtime/internal/atomic包里的Cas函数中,我们把这个实现拿出来改动一下,CMPXCHGL这条指令就是个CAS操作,我们在它后面用条件跳转指令构造一个循环,在CAS操作不成功时先执行PAUSE指令,然后循环尝试,直至CAS成功,需要注意的是,CMPXCHGL在替代不成功时,会把给定地址处的旧值写入AX寄存器中,所以我们需要在每次循环开始时重新为AX赋值,这种完全基于汇编实现还是有些复杂,实际上用汇编实现的只有PAUSE这条指令而已,所以我们不如单独实现一个汇编班的pause函数,由参数指定循环执行PAUSE指令的次数,然后使用这个pause函数结合标准库的atomic包,再写一个优化版的自旋锁,接下来指定PAUSE指令循环次数为30次

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在此运行这个函数,让我们看看CPU的使用情况,同样双核环境,CPU使用率居然没有什么变化,那么增加PAUSE指令还有什么用呢,再来看两次程序运行时间,增加PAUSE指令之前程序运行了16秒,增加PAUSE指令之后程序只运行6秒,

在这里插入图片描述

自旋锁实例代码请自取:https://github.com/eggo-tech/the-lock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值