使用 go-deadlock 库来定位 Go 协程信道中的 deadlock

Go

最近,我解决了一个反复出现的问题,其原因几周来一直不清楚。我的团队会“做某事” ^ 1^,然后问题就消失了,只剩下几天到一周的时间后来。但是,经过几个小时的调试,它才完全有意义。我只是在错误的地方寻找问题。我想我应该分享一下。

遇到得问题是这样的。每隔一周左右,我们就会从客户端收到一个错误报告,说明我们的Web应用程序加载时间很长,似乎根本没有加载,或者操作很慢。它似乎一次只发生在一个客户身上,我们都能够看到它发生时的行为。但是,通常在重新启动后勤服务或清理一些数据后将其清除。

但是,这次,我们的快速修复无效。应用程序未恢复。这是怎么回事?

正在等待轮到您

可以说,在我们为该应用程序提供的一项后勤服务中,每个组都有自己的Room。在将消息广播到会议室之前,我们已锁定成员列表,以避免任何数据争用或可能的崩溃。像这样:

func (r *Room) Broadcast(msg string) {
    r.membersMx.RLock()
    defer r.membersMx.RUnlock()
    for _, m := range r.members {
        if err := s.Send(msg); err != nil { // ❶
            log.Printf("Broadcast: %v: %v", r.instance, err)
        }
    }
}

请注意,我们等待直到每个成员收到消息,然后再继续下一个成员。稍后,这将成为问题。

另一个线索

测试人员还注意到,他们可以在重新启动服务后进入会议室,并且一切似乎都可以正常工作。但是,一旦他们离开并回来,该应用程序就会停止正常运行。原来,他们被挂在此功能上了,该功能向房间添加了一个新成员:

func (r *Room) Add(s sockjs.Session) {
    r.membersMx.Lock() // ❶
    r.members = append(r.members, s)
    r.membersMx.Unlock()
}

我们无法获得锁 aa ,因为我们的Broadcast函数仍在使用它来发送消息。

发现问题

初步调查表明,支持服务中的某些问题已被挂断,但是我们如何找出问题所在?

幸运的是,在跟踪实时互斥使用的工具go-deadlock的帮助下,我们可以看到这种情况正在发生。该工具会报告goroutine何时可以访问互斥锁30秒钟或更长时间^ 2^。该API反映了标准的Go库,从而使其成为一个便捷的插入检查器。结果指向Add函数,等待Broadcast函数释放其锁定。

突然之间,客户端报告变得完全有意义了(特别是当我们发现他们正在处理网络迟滞问题时)。

  1. 遭受高延迟的成员与其他成员一起加入会议室(Add)。
  2. 一旦他们提取了更新(Broadcast),所有成员便开始注意到更新缓慢。
  3. 成员重新加载应用程序,希望它可以解决问题,然后尝试重新加入(Add)。
  4. 但是,它们不能执行,因为他们正在等待(Broadcast)完成,因为高延迟成员已经放慢了它。

解决方案

由于我们需要锁定Broadcast中的锁以使我们的成员列表不发生变化,因此解决方案是在从锁中获得所需的内容后并行执行所有发送:

func (r *Room) Broadcast(msg string) {
    r.membersMx.RLock()
    defer r.membersMx.RUnlock()
    for _, m := range r.members {
        go func(s sockjs.Session) {
            if err := s.Send(msg); err != nil {
                log.Printf("Broadcast: %v: %v", r.instance, err)
            }
        }(m)
    }
}

这有一些优点:

  1. 没有成员需要等待另一个来获得广播消息。
  2. 成员无需等待即可加入会议室。
  3. 由于goroutine很便宜,并且套接字已经建立(通过WebSocket)。这样的多个异步调用应该不是问题

正如in the discussion,此解决方案无法保证消息会按顺序传递,也可能无法传递确定适合您的应用程序.

学到的经验

导致应用程序失败的这种特殊服务已经投入生产数月之久,没有出现任何此类已报告的问题,这导致错误的假设,即该服务每天处理数十万条消息,因此运行良好。但是,这不行。在适当的情况下,它暴露出一个明显的问题。

我现在打算问问我将来使用互斥锁或类似对象时的自己:当慢速I / O涉及由互斥锁保护的数据时,是否会导致不良行为?

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://wavded.com/post/golang-deadlocki...

译文地址:https://learnku.com/go/t/47451

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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