带缓冲通道的死锁

时间:2017-01-20 13:48:51

标签: go goroutine

我有一些代码是作业调度程序,并且正在从大量TCP套接字中整理大量数据。这段代码是对Large number of transient objects - avoiding contention方法的一种结果,它在很大程度上与CPU使用量有很大关系并且现在也没有锁定问题。

我的应用程序会不时锁定并且“通道长度”日志是唯一不断重复的东西,因为数据仍然来自我的套接字。然而,计数仍为5000,并且没有进行下游处理。

认为这个问题可能是一个竞争条件,而channel <- msg selectjobDispatcher可能会挂起它。麻烦的是我无法弄清楚如何验证这一点。

我怀疑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

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

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

2 个答案:

答案 0 :(得分:2)

我猜你的问题在与其他goroutine正在进行channel <- msg同时执行此shutdown <- id时遇到困难。

由于channelshutdown通道都没有被缓冲,它们会阻止等待接收器。而且他们可以陷入僵局,等待另一方变得可用。

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

或者通过发送关闭消息而不是发信号,您可以执行Google的上下文包所做的操作,并通过关闭关闭通道发送关闭信号。请查看https://golang.org/pkg/context/,尤其是WithCancelWithDeadlineDone函数。

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

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

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

答案 1 :(得分:-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

Aren你在这里放了一些东西(无缓冲?)

channel, ok := channelMap[msg.Avr]

所以你不需要清空那个频道才能在这里添加msg吗?

channel <- msg

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