在并发编程中,为了防止多个线程同时读写共享资源,我们需要互斥。Go标准库提供了一个互斥锁sync.Mutex,它通过锁定Lock()方法和解锁Unlock()方法实现对共享资源的并发控制。在之前的设计中,当持有锁时,其他goroutine试图获取锁会被阻塞。这种做法当然是合理的,但是在某些情况下,或许我们希望在获取锁失败的时候,不想停止执行,而是可以进入其他逻辑。在Go1.18中,sync.Mutex增加了一个新的方法TryLock(),这是一个非阻塞模式的锁操作。当调用TryLock()时,该函数简单地返回true或false,表明锁定是否成功。有了TryLock的存在,我们可以把这样的代码改成:m.Lock()//阻塞等待加锁成功的逻辑改成这样的逻辑ifm.TryLock(){//加锁的逻辑是successful}else{//加锁失败的逻辑}TryLock在Go精妙的mutex设计一文中实现。我们已经详细分析了mutex的设计。代码轻巧简洁。通过巧妙的位运算,只有一个状态字段就实现了四个字段的效果,非常精彩,推荐有兴趣的读者阅读。TryLock()的实现更简单。func(m*Mutex)TryLock()bool{old:=m.stateifold&(mutexLocked|mutexStarving)!=0{returnfalse}//可能有一个goroutine在等待互斥锁,但我们现在正在运行并且可以尝试在goroutine唤醒之前获取互斥锁。if!atomic.CompareAndSwapInt32(&m.state,old,old|mutexLocked){returnfalse}ifrace.Enabled{race.Acquire(unsafe.Pointer(m))}returntrue}当锁被其他goroutine占用时,或者当前锁处于饥饿模式,它会立即返回false。func(m*Mutex)Lock(){//快速路径:获取解锁的互斥体。ifatomic.CompareAndSwapInt32(&m.state,0,mutexLocked){ifrace.Enabled{race.Acquire(unsafe.Pointer(m))}return}//慢速路径(轮廓化以便快速路径可以内联)m.lockSlow()}而当锁可用时,TryLock()将尝试以与Lock()方法相同的方式获取锁。但是当获取失败时,不像Lock(),它不会自旋也不会阻塞。这是一个完全非阻塞的fetch方法。场景就像TryLock()方法的注释一样,它的使用场景不常见且不鼓励。//请注意,虽然确实存在TryLock的正确使用,但它们很少见,//并且使用TryLock通常是更深层次问题的标志//在互斥锁的特定使用中。在目前的Go1.18标准库源码中,内部大量使用了Lock()方法。完全不同的是没有地方可以使用TryLock()。只是在测试文件mutex_test.go中,新增了该方法的测试用例。这里讨论一下TryLock的使用场景:https://stackoverflow.com/questions/41788074/use-case-for-lock-trylock另外,开源社区中已经有很多GoTryLock的实现库。它们是通过CAS操作和基于sync.Mutex的不安全指针实现的;或使用渠道实施。但是这些库都不能进行种族检测。因此,有必要正式支持TryLock的实现,防止TryLock被滥用。并且因为可以集成种族检测,相比第三方库实现,开发者更容易发现问题。总结从2012年开始,关于Go加入TryLock的issue讨论其实已经很久了,直到Go1.18才加入。其中很大一部分原因是没有合法的理由来添加TryLock。此前GoTeam负责人rsc提出的反对意见:TryLock会鼓励开发者对锁的思考不精确,最终导致竞争条件。另外,Go1.18不仅为互斥锁sync.Mutex添加了TryLoc()方法,还为读写锁sync.RWMutex添加了相应的TryRLock()和TryLock()方法。就像这三个新方法的注释一样,虽然也有使用的情况,但是很少见,要慎用。
