在并发编程中,篇学为了避免多线程同时读写共享资源,篇学我们需要互斥。篇学Go 标准库提供了互斥锁 sync.Mutex ,篇学通过加锁 Lock() 方法和解锁 Unlock() 方法达到对共享资源的篇学并发控制。 在之前的篇学设计中,当锁被占有,篇学其他 goroutine 尝试获取锁时会被阻塞。篇学这种方式当然是篇学合理的,但是篇学在某些情况下,或许我们希望在获取锁失败时,篇学并不想停止执行,篇学而是篇学可以进入其他的逻辑。 在 Go 1.18 中,篇学为 sync.Mutex 新增了一个新的篇学方法 TryLock(),它是一种非阻塞模式的取锁操作。当调用 TryLock() 时,该函数仅简单地返回 true 或者 false,代表是否加锁成功。 有了 TryLock 的存在,我们就可以由这样的代码: m.Lock() // 阻塞等待加锁成功后的云服务器逻辑 转变成这样的逻辑 if m.TryLock(){ // 加锁成功的逻辑 }else { // 加锁失败的逻辑 } 在Go精妙的互斥锁设计一文中,我们详细分析过互斥锁的设计,其代码轻量简洁,通过巧妙的位运算,仅仅采用 state 一个字段就实现了四个字段的效果,非常之精彩,建议感兴趣的读者一读。 而 TryLock() 的实现更加简单。 func (m *Mutex) TryLock() bool { old := m.state if old&(mutexLocked|mutexStarving) != 0 { return false } // There may be a goroutine waiting for the mutex, but we are // running now and can try to grab the mutex before that // goroutine wakes up. if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) { return false } if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return true } 当锁被其他 goroutine 占有,或者当前锁正处于饥饿模式,它将立即返回 false。 func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } // Slow path (outlined so that the fast path can be inlined) m.lockSlow() } 而当锁可用时,TryLock() 会采用与 Lock() 方法一样的方式去尝试获取锁。但在获取失败时,与 Lock() 将不一样,它不会自旋或者阻塞。这是一个完全的非阻塞获取方式。 正如 TryLock() 方法的注释一样,它的应用场景并不常见,服务器租用并且也不被鼓励使用。 // Note that while correct uses of TryLock do exist, they are rare, // and use of TryLock is often a sign of a deeper problem // in a particular use of mutexes. 在当前 Go1.18 标准库源码中,与 Lock() 方法被大量内部使用而截然不同的是,并没有找到一处使用 TryLock() 的地方,仅仅在测试文件 mutex_test.go 中,有找到该方法的新增测试用例。 这里贴一个 TryLock 的使用场景讨论:https://stackoverflow.com/questions/41788074/use-case-for-lock-trylock 另外,在开源社区已经有不少 Go 的 TryLock 实现库。它们基于 sync.Mutex 通过 CAS 操作和 unsafe 指针实现 ;或者利用 channel 实现。 但是这些库都不能竞态检测。因此,官方支持实现 TryLock 是必要的,避免 TryLock 被滥用。且由于可以集成竞态检测,相较于三方库实现,有利于开发者发现问题。 从 2012 年开始,实际上很早就有关于 Go 增加 TryLock 的 issue 讨论,但是直到 Go 1.18 才被增加。这其中很大一部分原因是,并没有合理的源码库案例值得添加 TryLock。 Go Team 的负责人 rsc 之前提出的反对意见:TryLock 会鼓励开发者对锁进行不精确的思考,并最终导致竞态问题。 另外,Go 1.18 除了为互斥锁 sync.Mutex 新增了 TryLoc() 方法外,也为读写锁 sync.RWMutex 新增了相应的 TryRLock() 和 TryLock() 方法。 正如新增的这三个方法的注释,虽然使用它们的情况存在,但很少见,使用需谨慎。