【问题标题】:Deadlock with buffered channel缓冲通道死锁
【发布时间】:2017-01-21 22:46:19
【问题描述】:

我有一些代码是作业调度程序,正在整理来自大量 TCP 套接字的大量数据。这段代码是Large number of transient objects - avoiding contention 方法的结果,它在很大程度上降低了 CPU 使用率,现在锁定也不是问题。

我的应用程序有时会锁定,并且“通道长度”日志是唯一不断重复的内容,因为数据仍在从我的套接字传入。但是,计数仍为 5000,并且没有进行下游处理。

我认为这个问题可能是一个竞争条件,它可能挂断的线路是select 中的channel <- msg jobDispatcher。问题是我不知道如何验证这一点。

我怀疑由于 select 可以随机获取项目,所以 goroutine 正在返回,而 shutdownChan 没有机会处理。然后数据命中 inboundFromTCP 并阻塞!

有人可能会在这里发现一些非常明显的错误。并希望提供解决方案!?

var MessageQueue = make(chan *trackingPacket_v1, 5000)

func init() {
    go jobDispatcher(MessageQueue)
}

func addMessage(trackingPacket *trackingPacket_v1) {
    // Send the packet to the buffered queue!
    log.Println("Channel length:", len(MessageQueue))
    MessageQueue <- trackingPacket
}

func jobDispatcher(inboundFromTCP chan *trackingPacket_v1) {
    var channelMap = make(map[string]chan *trackingPacket_v1)

    // Channel that listens for the strings that want to exit
    shutdownChan := make(chan string)

    for {
        select {
        case msg := <-inboundFromTCP:
            log.Println("Got packet", msg.Avr)
            channel, ok := channelMap[msg.Avr]
            if !ok {
                packetChan := make(chan *trackingPacket_v1)

                channelMap[msg.Avr] = packetChan
                go processPackets(packetChan, shutdownChan, msg.Avr)
                packetChan <- msg
                continue
            }
            channel <- msg
        case shutdownString := <-shutdownChan:
            log.Println("Shutting down:", shutdownString)
            channel, ok := channelMap[shutdownString]
            if ok {
                delete(channelMap, shutdownString)
                close(channel)
            }
        }
    }
}

func processPackets(ch chan *trackingPacket_v1, shutdown chan string, id string) {
    var messages = []*trackingPacket_v1{}

    tickChan := time.NewTicker(time.Second * 1)
    defer tickChan.Stop()

    hasCheckedData := false

    for {
        select {
        case msg := <-ch:
            log.Println("Got a messages for", id)
            messages = append(messages, msg)
            hasCheckedData = false
        case <-tickChan.C:

            messages = cullChanMessages(messages)
            if len(messages) == 0 {
                messages = nil
                shutdown <- id
                return
            }

            // No point running checking when packets have not changed!!
            if hasCheckedData == false {
                processMLATCandidatesFromChan(messages)
                hasCheckedData = true
            }
        case <-time.After(time.Duration(time.Second * 60)):
            log.Println("This channel has been around for 60 seconds which is too much, kill it")
            messages = nil
            shutdown <- id
            return
        }
    }
}

2016 年 1 月 20 日更新

我尝试将 channelMap 重新设计为具有一些互斥锁的全局变量,但最终还是死锁了。


稍微调整了代码,仍然锁定,但我不明白这个是怎么做的!! https://play.golang.org/p/PGpISU4XBJ


2017 年 1 月 21 日更新 在一些建议之后,我将其放入一个独立的工作示例中,以便人们可以看到。 https://play.golang.org/p/88zT7hBLeD

这是一个长时间运行的过程,因此需要在机器上本地运行,因为游乐场会杀死它。希望这将有助于深入了解它!

【问题讨论】:

  • 在死锁发生时获取堆栈跟踪,并查看每个 goroutine 被阻塞的位置。
  • 我试过了,但是有 300 个左右的 goroutine,无法确定块在哪里!
  • 是否有任何 pprof 命令对这里有帮助?
  • 如果您在inboundFromTCP 的情况下,并且相应的processPackets 关闭,它不会从地图中删除,因此您会遇到死锁。这有点过度使用频道的味道,但我没有清楚地了解问题,所以我不确定该向您发送哪个方向。
  • 不要听那些讨厌频道的人。是的,通道就是锁,就像互斥锁一样。但是通道架构可以非常好。一旦你发现了你所看到的错误。

标签: go goroutine


【解决方案1】:

我猜你的问题是在执行 channel &lt;- msg 的同时,另一个 goroutine 正在执行 shutdown &lt;- id。

由于channel 和shutdown 通道都没有被缓冲,它们会阻塞等待接收器。他们可以死锁等待对方可用。

有几种方法可以修复它。您可以使用 1 的缓冲区声明这两个通道。

或者,您可以像 Google 的上下文包所做的那样,通过关闭关闭通道来发送关闭信号,而不是通过发送关闭消息来发出信号。查看https://golang.org/pkg/context/,尤其是WithCancel、WithDeadline 和Done 函数。

您也许可以使用上下文来删除您自己的关闭通道和超时代码。

JimB 有一点关于关闭 goroutine,而它可能仍在通道上接收。您应该做的是发送关闭消息(或关闭,或取消上下文)并继续处理消息,直到您的ch 通道关闭(使用case msg, ok := &lt;-ch: 检测),这将在收到关闭后发生发件人。

这样你就可以得到所有在关闭实际发生之前传入的消息,并且应该避免第二次死锁。

【讨论】:

【解决方案2】:

我是 Go 新手,但在此代码中

case msg := <-inboundFromTCP:
        log.Println("Got packet", msg.Avr)
        channel, ok := channelMap[msg.Avr]
        if !ok {
            packetChan := make(chan *trackingPacket_v1)

            channelMap[msg.Avr] = packetChan
            go processPackets(packetChan, shutdownChan, msg.Avr)
            packetChan <- msg
            continue
        }
        channel <- msg

你不是在通道中放了一些东西(无缓冲吗?)

channel, ok := channelMap[msg.Avr]

那么在您可以在此处添加味精之前,您不需要清空该频道吗?

channel <- msg

就像我说的,我是 Go 新手,所以我希望我不会傻。 :)

【讨论】:

  • 不,channel, ok 正在从地图中获取存储的频道。如果它不存在,则ok 为 false,代码创建一个通道并将其设置在地图中。
  • 谢谢你的解释,赞!
  • 我希望人们不要再否决我的回答。问这个问题的人还要求第二双眼睛。我知道我的回答很有可能是错误的,但有时只是贡献有助于其他人以不同的方式看待问题或考虑他们以前没有想到的事情。 Zan Lynx 似乎就是这种情况,因为他的回答似乎表明他开始考虑可能存在 channel
  • 我上面的评论绝对不应被解释为我试图为 Zan 的回答而努力。他显然比我在这方面的知识渊博得多,我很感激他向我提供的解释。我只是说,有时即使是错误的答案也有助于导致正确的答案,因为他们专注于以前没有考虑过的领域,我认为这不应该受到惩罚。
猜你喜欢
  • 1970-01-01
  • 2017-09-06
  • 2021-12-25
  • 2021-08-15
  • 2014-05-26
  • 1970-01-01
  • 2019-09-15
  • 1970-01-01
  • 2017-10-05
相关资源
最近更新 更多