大家好,我是建宇。还记得之前写过一篇文章《Go 为什么不支持可重入锁?》,主要是介绍有其他语言经验的朋友,想让Go支持可重入锁,被狠狠拒绝了。来自《Go 为什么不支持可重入锁?》虽然不是可重入锁,boss总会过得不好。但是在Go1.18中,实现了一个新的尝试获取锁的方法(TryLock),也有点那个味道。今天建宇就带大家来了解一下被折腾了3次的“他”。后台更新的新功能必然涉及到用户场景。时间来到2018年,@deanveloper提到了一个经典的加载场景:需要加载几个非常大的文件,我想要一个进度条来显示我离完成还有多远。他认为这个进度条可以用TryLock来实现。以下是他的示例代码:func(b*ProgressBar)Add(nint){atomic.AddInt64(&b.Progress,int64(n))ifb.Progress>=b.Max{b.once.Do(b.updateClientsDone)return}ifb.pctMx.TryLock(){deferb.pctMx.Unlock()b.updateClients()}}上面代码的基本逻辑就是不断更新计数器,然后实现他的滚动加载进度bar通过尝试获取锁。因为老板们觉得用channel+select-default来做会更好,这个用例不足以支持TryLock的功能增加,又被拒了。经过2013年和2018年的大量讨论,时间又来到了2021年。@TyeMcQueen举了一些h2库的例子,表达了对TryLock方法会更好的一些期待。但是也被否决了,RussCox反对的理由是:互斥体是用来保护不变量的。如果锁由其他人持有,那么您对不变量无话可说。TryLock方法鼓励对锁的不精确思考;它鼓励对可能正确或可能不正确的不变量做出假设。这最终成为它自己的竞争来源。在之前的失败案例中,RussCox认为给出的案例不足以说服加入TryLock系列方法的理由。我觉得需要补充的人越来越多了,谷歌老大DmitryVyukov给出了如下案例:就是gvisor、v.io/x/lib/nsync、trivago/tgo等软件库等都是使用TryLock这类方法的实现,与模拟代码基本一致。最后RussCox放手了,说:“每个人都同意这是不幸的,但有时是必要的”,感觉勉强同意了。考虑是给一个官方的实现,而不是各种第三方的TryLock方法,效率很低,重复。历史的整体时间线是这样的:@lukescott在2013年提出了《sync: mutex.TryLock》,被拒绝了。2018@deanveloper提议《proposal: add sync.Mutex.TryLock》,被拒绝。2021@TyeMcQueen提议《sync: add Mutex.TryLock》,先被拒绝,再被接受。2022年,由于Go1.17的特性已经冻结,计划在Go1.18(3月)发布。即将到来的Go1.18中的新方法sync.TryLock,主要是增加了sync标准库中TryLock系列的相关方法。如下图所示:sync@master#Mutex.TryLockMutex.TryLock:尝试锁定互斥量,是??否成功返回。RWMutex.TryLock:尝试加锁读写锁,是否成功返回。RW互斥锁。试试RLock。尝试加读锁,返回是否成功。官方提醒:虽然使用TryLock的场景确实存在。但应该很少见,经常使用TryLock可能是更深层次问题的迹象。综上所述,在Go1.18中,尝试获取锁的TryLock方法终于落地。这种方法的存在有利也有弊。看来以后可能会成为if-else的常用判断,也可以避免很多锁导致的long-hold。但是,从应用程序设计的角度来看,使用这种方法是有问题的,需要特别注意和思考。
