造轮子是什么意思_造轮子系列——写spinlock

b3d35cbd81dac2e21e4529b5c6299db6.png

spinlock即自旋锁,通常被挂上“快”和“浪费”的标签。其实大家现在普遍使用的锁里基本都有自旋成分,但是这并不妨碍我们造轮子。╮(╯3╰)╭

C++11这个大版本带来了多线程内存模型,同时也带来了thread、atomic、mutex等实用库。得益于此,自旋锁的实现非常简单。

atomic里有个atomic_flag,只能表示true和false两种状态,并且只有“test&set”和“clear”两种功能,用来做自旋锁非常合适。

struct 

简单来说test&set会返回修改前的状态,并把状态置为true,clear则把状态置为false。

“获得锁”这个过程就可以表述为“状态从false变为true”,用test&set刚好。对应的解锁就成了clear。

好的,今日份的轮子就造到这里。<( ̄︶ ̄)↗


等等!

作为一个造轮子爱好者,一个简单的自旋锁显然是没法满足的!

所以要把自旋锁玩出花!๑乛◡乛๑


自旋锁同一时间只能有一个拥有者,而我们平时的操作通常有读和写两种,读与读之间没有冲突,但写与所有操作都冲突。换句话说,两个状态是不足以表达我们的需求的

没关系,atomic里还有一系列原子操作,我们拿个atomic<uint32_t>来,能表达的状态总够了吧。“加锁”和“解锁”依旧可以表达成“从一个状态到另一个状态”。atomic的特化使得数学加减运算与位运算都是原子的,我们可以把这个uint32_t的数值看作状态,用运算来实现状态间的转换。(ง •_•)ง

写锁是独占的,而读锁却可以有很多份。从二进制角度去看uint32_t,我们就能得出一个解决方案——MSB(most significant bit),即最高位归写锁,剩余位归读锁

再回过头看最原始的自旋锁,之所以test&set要返回修改前状态,是因为我们要根据修改前状态来判断是否发生了“状态改变”,即加锁是否成功。对于更多数量的状态,我们可以使用CAS(compare&exchange),它的作用是将现有状态和预期状态比较,如果相同就修改成新状态

更为方便的是,如果修改不成功,它还会把现有状态覆盖到“预期状态”,省去了我们重新获取状态的开销。

struct 

分析一下思路,解锁很简单,依旧是不加任何判断直接减去对应的值。而加锁部分,由于读锁和写锁互斥,所以需要额外判断。

读锁加锁时,我们只要和写锁争抢,所以预期状态是“没有写锁”,要把MSB置空。此时CAS操作失败有三种可能:1.有写锁,MSB和预期不符。2.读锁争抢失败,和预期不符。3.前两者皆有。而我们其实不用在乎到底是哪种情况,只要不断重试就行了。

写锁加锁则分为两段,第一段是和其他写锁争抢,第二段是和其他读锁争抢。和写锁争抢时我们做的和读锁其实差不多,同样是预期“没有写锁”,置空MSB,不断重试。

第二阶段和读锁争抢就不一样,我们的预期是“没有读锁”,故不断重试读取,直到只剩下MSB


前面说到了,写锁和读锁的竞争发生在第二阶段,即哪怕有读锁在,写锁也能完成第一阶段。而当写锁完成第一阶段后,读锁就不能继续上锁了_(:з)∠)_

从上述分析不难看出,写锁的优先级是高于读锁的。而如果我们认为读比写重要,想要让读锁优先级更高怎么办?

有人可能会觉得很简单——把和读锁的竞争放到第一阶段不就行了?ㄟ( ▔, ▔ )ㄏ

其实不然,想要和读锁分出胜负,唯一的办法就是标记上MSB。而这同时又是在和写锁竞争,即没法在不和写锁争抢的前提下和读锁争抢(°ー°〃)

但有了上面的结论,解决方法就很简单了——不分两阶段,直接和其他所有锁一起竞争不就行了?

struct 

可以看到唯一更改就是写锁加锁的逻辑。由于写锁要和其他所有锁争抢,故状态变化的起点只有一个,我们的预期是“没有任何锁”,即0。而状态的终点为“只有一个写锁”。

这样一来,读锁依旧要看写锁的脸色,但写锁却没法在读锁存在的情况下做任何修改,读锁的权益被极大地得到了保证。


读优先的锁看起来代码更简洁,但写优先的锁也不是没有优点。由于写优先加锁时有两个阶段,我们不由得产生了一个设想,如果消除写锁之间的冲突,只有读和写之间需要争抢,而他们内部不需要争抢呢?w(゚Д゚)w

这样一来我们就不好叫读写锁了,应该叫偏心锁,因为有一方的优先级更高。修改的地方也不多,只要把写锁第一阶段改为不争抢,然后把bit平分给两者就行了。

struct 

就这样吧,至于锁空转的性能问题和解决方案,那就是涉及到CPU设计、NUMA配置、OS调度等的大学问。

造轮子的乐趣在于造出了轮子,而不在于轮子跑得快不快,不是么。(๑•̀ㅂ•́) ✧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值