Go: 互斥锁与饥饿

本文使用Go版本:1.13

在使用Golang开发时,当尝试获取一个永远也得不到的互斥锁时,它就可能发生饥饿问题。本文我们将研究Go1.8版本饥饿问题。该问题在Go1.9版本已经解决。

饥饿

为了更好解释互斥锁饥饿情况,我们将以以下示例讨论并演示其改进。

func main() {
  done := make(chan bool, 1)
  var mu sync.Mutex

  // goroutine 1
  go func() {
    for {
      select {
        case <-done:
          return
        default:
          mu.Lock()
          time.Sleep(100*time.Microsecond)
          mu.UnLock()
      }
    }
  }

  // goroutine 2
  for i := 0; i < 10; i++ {
    time.Sleep(100*time.Microsecond)
    mu.Lock()
    mu.UnLock()
  }

  done <- true
}

本示例使用了两个协程:

  • 协程1长时间持有锁并迅速释放
  • 协程2短暂持有锁并长时间释放

二者都有100微妙的周期,但协程1不断请求获取锁,可以预见的是其将不断持有该锁。

下面时使用Go1.8完成的示例,并打印协程2迭代10次之后锁分配情况:

每个goroutine获取锁情况:
g1: 7200216
g2: 10

可以看的第二个协程获取10次锁的时候,第一个协程获取锁的次数远超10次。我们来分析发生了什么。

首先,协程1获取锁并休眠100微秒。当第二个协程尝试获取锁时,将被加入锁队列(FIFO),且该协程将进入等待状态:

Go: 互斥锁与饥饿

然后,当协程1完成它的工作时,将释放锁。并通知队列唤起协程2。协程2将被标记为可运行状态并等待调度器调起:

Go: 互斥锁与饥饿

然而,当协程2正等待执行时,协程1将再次请求锁:

Go: 互斥锁与饥饿

当协程2尝试获取锁时,将发现该锁已被持有并进入等待模式,如下图:

Go: 互斥锁与饥饿

故而协程2能获取锁将取决于其在线程上运行所花费的时间。

现在的问题已经确定,让我们尝试解决它可行的方案。

桥接 VS 切换 && 自旋

有会多方式可以解决互斥锁,例如:

  • 桥接。这是为提高吞吐量儿设计的。当锁释放时,它将唤醒第一个等待着,并且将锁给第一个传入的请求者或者当前等待着。

Go: 互斥锁与饥饿

这就是Go1.8的设计方式,即我们最开始示例所看到。

  • 切换。释放时,互斥锁将持有该锁,直到第一位等待着准备获取它。这种方式会减少吞吐量,因为即使有协程已经准备获取锁,锁也会互斥锁被持有。

Go: 互斥锁与饥饿

我们能在Linux内核锁中找到这种逻辑。

饥饿状态是可能会出现的,因为该方式运行窃取锁。正在运行的任务会优先于即将被唤醒或已被唤 醒的协程获得锁。

窃取锁是一项重要的性能优化,因为等待唤醒的协程并获得锁可能会花费很多时间。在此期间,每个协程都会在该锁上停顿。

这会重新引入新的等待时间,因为一旦我们进行协程切换,其他协程就只能等待唤醒。

这种方式下,锁切换很好平衡两个协程之前锁的分配,但它也降低了性能,因为它会强制第一个协程等待锁即使它没有被持有。

  • 自旋。互斥锁不同于自旋锁,它必须结合一些逻辑。当等待队列为空或当应用程序大量使用互斥锁时,自旋在这个时候是有用的。取消或唤起协程都有成本,并且可能比自旋等待下一个锁获取更慢。

Go: 互斥锁与饥饿

Go1.8便使用了这种策略。当尝试获取一个已持有的锁时,如果本地队列为空并且处理器数量大于1,则协程将会自旋几次。当使用一个处理器处理自旋时,程序会处于阻塞状态。自旋过后,该协程将会停止。在程序大量使用锁的情况下,这是一种有效的方式。

饥饿模式

在Go1.9之前,Go结合了桥接于自旋模式。而Go1.9版本,Go通过添加新的饥饿模式来解决之前的问题,该模式将导致解锁模式期间的切换。所以等待的协程超过1毫秒,也成为有界等待,将被标记为饥饿。被标记为饥饿的协程,解锁时会将该锁交给第一位进入等待的协程。工作模式如下:

Go: 互斥锁与饥饿

在饥饿模式下,自旋也可能会停止。因为传入的协程没有机会获取锁。

使用Go1.9版本在饥饿模式下运行之前的用例:

每个goroutine获取锁情况:
g1: 57
g2: 10

当前结果就比较公平了。

原文地址:medium.com/a-journey-with-go/go-mu...
译文地址:博客:Go: 互斥锁与饥饿

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!