Golang Mutex互斥锁实例代码分析


本篇内容介绍了“GolangMutex互斥锁实例代码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Mutex就是一把互斥锁,可以想象成一个令牌,有且只有这一个令牌,只有持有令牌的goroutine才能进入房间(临界区),在房间内执行完任务后,走出房间并把令牌交出来,如果还有其余的goroutine等着获取这个令牌,让他们再去抢这个令牌,抢到的重复上述过程,没抢到的继续等。上述是从宏观角度来看待互斥锁的,但是在Mutex内部,有着非常复杂的抢锁逻辑,Mutex的发展也经历了几个版本,我们可以用拿令牌进餐厅吃饭来形象比喻下几个主要版本的变化。前提:餐厅一次只能进入一个人,餐厅有一个令牌,只有持有这个令牌的人才能进去;从餐厅出来后,需要把这个令牌归还版本一餐厅在门外设置了一个队伍,如果令牌空闲,拿着令牌去餐厅用餐;如果令牌不是空闲的,新来的人就要去队伍后面排队等待叫号。(不是空闲包含两种情况:持有令牌的人在餐厅里面,队伍是空的;队伍有人排队。)此版本的问题就是:只要令牌不是空闲的,新来的人必须直接去排队,没有商量的余地。这样看起来很公平,遵循先来后到的原则,但是对于餐厅来说,营业效率就会有所降低,即单位时间内接待顾客的数量(IO)会减少。为什么这样说呢,举个例子,有个顾客从餐厅出来归还令牌后,需要去等待队列去叫号,被叫到号的这个人需要花费时间走到餐厅(获取到CPU),这中间就浪费了不少时间。版本二为了提高营业效率,允许刚到门口的顾客和被叫到号的顾客一起去抢令牌,而不是直接去排队,这样就给了新人机会。举个例子:当持有令牌的人从餐厅出来归还令牌后,去等待队列叫个号,如果此时有顾客刚到门口,被叫到号的和新到的顾客一起抢令牌,抢到的就可以直接进入餐厅,抢不到的接着去排队,由于刚到的顾客离门口近(正在占据CPU),被叫到号的顾客离得远(需要等CPU),而且刚到的顾客可能不只一个,所以被叫到号的顾客很大概率抢不到令牌,可能还没走到门口(还没获取到CPU)就被新来的顾客抢走了。不管怎么样,这样提高了餐厅的效率,可以在单位时间内接待更多的客户。版本三餐厅发现有些人用餐很快,如果让抢不到令牌的先别直接去排队,而是在门口转悠会(当然不能一直转悠,有条件限制,到了限制还是要去排队),这种方式类似乐观锁,那么有顾客从餐厅出来后,就不用去叫号了,直接让门口的这些顾客继续抢就行了,这样就进一步提高了餐厅的运行效率,毕竟叫号真的太浪费时间了。版本四经过了多个版本的优化,餐厅的运营效率是越来越高了,但是有些人可要准备要骂娘了,这些人是谁呢,当然是已经在队伍里等待的那些人。由于给了新人机会,如果持续有新顾客来,那么已经在队伍里的那些人永远也拿不到令牌,可真的要饿死了。Mutex在这个版本只为三件事:公平、公平、还是tm的公平!坚持让每一个人都不饿肚子的原则,餐厅搞出了一个新的模式:饥饿模式。如果有顾客等的时间超过了阈值(1ms),餐厅变为饥饿模式,在该模式下,所有新来的顾客直接去排队,然后按照先来先到的顺序,依次将令牌给等待队列队首的顾客。那么什么时候由饥饿模式变为正常模式呢?当拿到令牌的顾客发现自己从等待到拿到令牌的时间小于阈值(1ms)了,或者等待队伍没人等了,此时餐厅就变为正常模式,毕竟上述两个条件都说明当前餐厅竞争不是很激烈了。同时这个版本修复了以前的一个问题:之前从等待队列唤醒的顾客如果没有抢到令牌,再回到队列后是插到队尾,这样对已经排到第一位的顾客太不友好了。在这个版本中修复了该问题,唤醒的顾客如果没有抢到令牌,直接插入到队首,下次叫号还是他。特性总结经过了多次迭代,目前的版本有了如下特性:给新人机会:让刚来的顾客和从队列唤醒的顾客一起去抢令牌,唤醒也是按照先来先到的原则唤醒;保持乐观态度:没抢到不是直接去排队,而是可以在门口转悠会,说不定里面的人马上就出来了;正常模式和饥饿模式的切换:为了公平起见,正常模式下给了新人机会,一起去抢令牌;饥饿模式下照顾老人,所有人老老实实排队,按照先来先到的顺序拿令牌。整个餐厅既保持了公平,又提高了运行效率,一切井然有序起来了。回归正题让我们从餐厅回到Go中来,Mutex有两种模式:正常模式和饥饿模式:正常模式下,如果当前锁正在被持有,抢不到锁的就会进入一个先进先出的等待队列。当持有锁的goroutine释放锁之后,按照从前到后的顺序唤醒等待队列的第一个等待者,但是不会直接给被唤醒者锁,还是需要他去抢,即在唤醒等待队列等待者这个时间,同时也会有正在运行且还未进入等待队列的goroutine正在抢锁 (数量可能还很多),这些都会和刚唤醒的等待者一起去抢,刚唤醒的可能还没有分到CPU,而正在运行的正在占据了CPU,所以正在运行的更有可能获取到锁,被唤醒的等待者可能抢锁失败。如果等待者抢锁失败,他会被放到等待队列的队首,如果超过1ms都没抢到锁,就会从正常模式切换到饥饿模式。饥饿模式下,要释放锁的goroutine会将锁直接交给等待队列的第一个等待者,不需要去抢了,而且新来的goroutine也不会尝试去抢锁,直接加入到等待队列的尾部。那么什么时候会从饥饿模式切换到正常模式呢:(1)如果当前被唤醒的等待者获得到锁后,发现自己是队列中的最后一个,队列中没有其他等待者了,此时会切换到正常模式(2)如果当前被唤醒的等待者获得到锁后,发现自己总共的等待时间不超过1ms,就获得到锁了,此时也会切换到正常模式正常模式会带来更高的吞吐量:一个goroutine要释放锁,更大可能会被正在运行的goroutine抢到,这就避免了协程的上下文切换,运行更多的goroutine,但是有可能造成一个问题,就是锁始终被新来的goroutine抢走,在等待队列中的等待者始终抢不到锁,这就会导致饥饿问题。饥饿模式就是为了解决这个问题出现的,保证了每个goroutine都有运行的机会,防止等待时间过长。信号量 sema 就相当于我们说的令牌,state免费云主机域名 是 int32 类型,一共 32位,通过每个位记录了当前的状态:state字段mutexLocked:当前是否已经上锁,state & mutexLocked = 1表示已经上锁;mutexWoken:标记当前是否有唤醒的 goroutine,state & mutexWoken = 1表示有唤醒的goroutine;mutexStarving:当前是否为饥饿状态,state & mutexWoken = 1表示处于饥饿状态;mutexWaiterShift:29位,state >> mutexWaiterShift得到等待者的数量;Lock()加锁方法分为两部分,第一部分是fast path,可以理解为快捷通道,如果当前锁没被占用,直接获得锁返回;否则需要进入slow path,判断各种条件去竞争锁,主要逻辑都在此处。了解过原子操作的同学,对CompareAndSwap(CAS)应该不陌生,CompareAndSwapInt32(addr *int32, old, new int32)有三个参数,如果地址addr指向的值与old相等,则将addr的值改为new,否则不变,也就是说在我们修改前,如果有人修改了addr指向的值,本次修改就会失败。加锁的这部分代码,新来的goroutine或者从队列里面唤醒的goroutine都会进入如下逻辑,相当于给新人机会:1.乐观态度的自旋:判断是否可以自旋,如果可以自旋,就自旋等待;如果有可能,把唤醒标记位置为1,标记外面有唤醒的goroutine,释放锁的时候就不会去队列里面唤醒了,毕竟已经有人在等待了;2.修改系统状态:跳出自旋后,每个goroutine根据当前系统状态修改系统状态:非饥饿状态,想要加锁(如果本来就是加锁状态,将加锁位 设置为 1 相当于不变)锁被占用 或者 处于饥饿模式下,新增一个等待者当前goroutine已经进入饥饿了,且锁还没有释放,需要把Mutex的状态改为饥饿状态如果当前goroutine是被唤醒的,清除系统唤醒标记3.利用CAS修改系统状态,同一时刻只有一个goroutine能够设置成功,但是设置成功并不代表获取到锁了:之前是非上锁的正常状态,设置成功说明本次抢锁成功,可以返回去操作临界区了;之前是上锁状态或者饥饿状态,本次只是新增了一个等待者,然后根据是否是新来的,去队列队尾或者队首排队,等待叫号;4.从队列中被叫号唤醒,不一定是获取到锁了:当前是饥饿状态,那么一定是获取到锁了,因为饥饿状态只把锁给队列的第一个goroutine非饥饿状态,将自己状态置为唤醒,再去抢锁,重复上述过程问:系统会不会同时存在唤醒标志和饥饿标志都为1的情况呢?答:不会。只有等待时间大于1ms的才会去设置饥饿标记,也就是只有从队列唤醒的才会去设置,那么从队列中唤醒的goroutine,自身的awoke=true,每当去设置饥饿标记的时候会把唤醒标记清除。Unlock()解锁方法也分为两部分,第一部分是fast path,可以理解为快捷通道,直接把锁状态位清除,如果此时系统状态恢复到初始状态,说明没有 goroutine 在抢锁等锁,直接返回,否则进入slow pathslow path会根据是否为饥饿状态,做出不一样的反应:正常状态:唤醒一个goroutine去抢锁,等待者数量减一,并将唤醒状态置为1;饥饿状态:直接唤醒等待队列队首的goroutine,锁的所有权直接移交(修改等待者数量、是否取消饥饿标记,由唤醒的goroutine去处理)。“GolangMutex互斥锁实例代码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注百云主机网站,小编将为大家输出更多高质量的实用文章!

相关推荐: Node.JS怎么使用纯JavaScript生成图片或滑块式验证码功能

本篇内容介绍了“Node.JS怎么使用纯JavaScript生成图片或滑块式验证码功能”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!有一些Node.JS图片生成…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 05/09 18:17
下一篇 05/09 18:18

相关推荐