Go 的 TryLock 实现

在并发编程中,为了避免多线程同时读写共享资源,我们需要互斥。Go 标准库提供了互斥锁 sync.Mutex ,通过加锁 Lock() 方法和解锁 Unlock() 方法达到对共享资源的并发控制。

在之前的设计中,当锁被占有,其他 goroutine 尝试获取锁时会被阻塞。这种方式当然是合理的,但是在某些情况下,或许我们希望在获取锁失败时,并不想停止执行,而是可以进入其他的逻辑。

在 Go 1.18 中,为 sync.Mutex 新增了一个新的方法 TryLock(),它是一种非阻塞模式的取锁操作。当调用 TryLock() 时,该函数仅简单地返回 true 或者 false,代表是否加锁成功。

有了 TryLock 的存在,我们就可以由这样的代码

m.Lock()
 // 阻塞等待加锁成功后的逻辑

转变成这样的逻辑

if m.TryLock(){
 // 加锁成功的逻辑
 }else {
 // 加锁失败的逻辑
 }

TryLock 实现

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 实现。

01193e85e45ee9366103d50fd49f8063.png

但是这些库都不能竞态检测。因此,官方支持实现 TryLock 是必要的,避免 TryLock 被滥用。且由于可以集成竞态检测,相较于三方库实现,有利于开发者发现问题。

总结

从 2012 年开始,实际上很早就有关于 Go 增加 TryLock 的 issue 讨论,但是直到 Go 1.18 才被增加。这其中很大一部分原因是,并没有合理的案例值得添加 TryLock。

Go Team 的负责人 rsc 之前提出的反对意见:TryLock 会鼓励开发者对锁进行不精确的思考,并最终导致竞态问题。

另外,Go 1.18 除了为互斥锁 sync.Mutex 新增了 TryLoc() 方法外,也为读写锁 sync.RWMutex 新增了相应的 TryRLock() 和 TryLock() 方法。

正如新增的这三个方法的注释,虽然使用它们的情况存在,但很少见,使用需谨慎。

往期推荐

a255200229adf8047eda8a95901c6a90.png

机器铃砍菜刀

欢迎添加小菜刀微信

加入Golang分享群学习交流!

感谢你的点赞在看哦~

a6bc5dab7797144b2933806867e5328c.gif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 中的 tryLock 方法可以用来实现自旋锁。tryLock 方法尝试获取锁,如果获取成功,则立即返回 true,否则返回 false。可以在循环中调用 tryLock,直到成功获取锁为止。这样就可以实现自旋锁的效果。 例如: ``` Lock lock = new ReentrantLock(); while(!lock.tryLock()){ //do something } try{ //critical section }finally{ lock.unlock(); } ``` 这样做的缺点是会占用CPU资源,如果锁竞争激烈的话会导致性能问题。 ### 回答2: Java中的tryLock()方法是java.util.concurrent.locks.ReentrantLock类中的一个方法,用于实现自旋锁。自旋锁是一种基于循环的锁,当线程尝试获取锁时,如果发现锁已被其他线程持有,则不会进入等待状态,而是通过循环不断尝试获取锁,直到获取成功为止。 tryLock()方法可以尝试获取锁,如果锁当前没有被其他线程持有,则获取锁成功并返回true;如果锁已被其他线程持有,则获取锁失败,并立即返回false,不会阻塞线程。使用该方法可以避免线程进入等待状态,减少线程切换的开销,提高程序的执行效率。 tryLock()方法还提供了重载方法,可以设置超时时间,在限定的时间内尝试获取锁。如果超过指定的时间仍未获取到锁,则放弃获取,返回false。通过设置超时时间,可以防止线程长时间等待,避免可能的死锁情况发生。 自旋锁在某些场景下可以提高程序的性能,特别是对于锁的竞争不激烈、持有锁的时间较短的情况。但是在一些高并发场景下,长时间的自旋可能会消耗大量的CPU资源,导致程序性能下降。因此,需要根据具体的业务场景来选择合适的锁机制。 综上所述,JavatryLock()方法实现了自旋锁,通过不断尝试获取锁而不进入等待状态,提高了程序的执行效率。但是需要注意在高并发场景下的使用,避免长时间的自旋带来的性能问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值