Enque和deque使频道死锁

时间:2014-09-27 23:51:09

标签: concurrency go deadlock race-condition goroutine

我正在尝试在单个频道中实现队列,出队和重新排队。

我有两个问题:

  • 为什么我会陷入僵局?我期待一个无限循环(因为我重新排队甚至生成更多元素的元素等等)。 range queue总是不应该收听频道吗?

  • 这是deadloks之前的印刷品的一部分:

    enqueueing 1000 enqueueing 1001 dequeued 1001 dequeued 1001 using 1001 using 1001

两个不同的goroutines是否将相同的元素排队?我不明白为什么这个数据竞争;我认为range每次都会选一个。

Code in Playground

func main() {

    queue := make(chan int)
    start := 10

    go func() { queue <- start }()

    for element := range queue {
        fmt.Println("dequeued ", element)
        go enqueue(element, queue)
    }
}

func enqueue(element int, queue chan int) {
    fmt.Println("using ", element)
    if element%2 == 0 {
        fmt.Println("creating new elements from ", element)
        var news = []int{element * 100, element*100 + 1}

        for _, new := range news {
            fmt.Println("enqueueing ", new)
            go func() { queue <- new }()
        }

    }
}

2 个答案:

答案 0 :(得分:4)

这是一个范围问题。开始使用go func() {...}时会发生 lot ;它可能比并发的真正问题更常见。 the FAQthe Go wiki中有关于它的部分。

您创建的匿名func会在外部作用域中获取对变量的引用,而不会在func时对其进行引用声明运行。并且每次循环都会更新循环变量,因此它会在您的go语句和实际运行的goroutine之间发生变化。

您可以通过在循环的每次迭代期间(即在for括号内)声明另一个变量来解决此问题。如果您的循环为for i := range arr {...},则只需添加i := i即可。所以,这个糟糕的版本:

arr := make([]int, 10)
for i := range arr {
    go func() { 
        fmt.Println(i)
    }()
}

... always prints 9。一个固定版本,在循环中重新声明i

arr := make([]int, 10)
for i := range arr {
    i := i
    go func() { 
        fmt.Println(i)
    }()
}

... prints 0-9。重新宣布i的一种不同的,可以说更优雅的方式是将其作为匿名func的一个参数;那么这不是一个奇怪的独立声明:

arr := make([]int, 10)
for i := range arr {
    go func(i int) { 
        fmt.Println(i)
    }(i)
}

Here's that code。对于所有Playground版本,我必须添加同步,以便在goroutines运行之前main不会退出。

令人困惑的是,每次通过循环“运行”一次的声明表现不同(范围显示的奇怪方式)但是绕过它的方法很简单。


在您的情况下:queue <- new可以随时运行,并且在您完全通过for _, new循环后运行结果。但是,它使用new 实际运行时的值,而不是执行go语句时的 。在这种情况下,你开始的两个goroutine都得到值1001,所以通过两个传递给enqueue的值的第二次是奇数(你可以在输出中看到两个using 1001)因此没有任何写入队列,因此range queue循环没有任何内容可供使用。频道也没有关闭,因此main不能只是结束,所以你会陷入僵局。

您希望每次执行goroutine时“捕获”不同的值。为此,您可以将new := new放在循环的顶部,这看起来很有趣。这足以使每个迭代的值与Go的视角“不同var”,因此您可以在频道中插入10001001

一旦你真正开始工作,Playground将无法成功运行你的代码,因为它永远循环并且有很多goroutines在运行,我猜Playground不喜欢它(过度使用资源)。如果您在退出前添加了100个元素的限制,则会获得http://play.golang.org/p/bBM3uTnvxi。你还会注意到它输出的数字很奇怪,因为每次乘以100会最终溢出机器的int类型,但这就是用低级语言编写程序的方式。

作为次要的事情,您可能不想命名变量new,因为它也是内置函数的名称。这样做是合法的,只是令人困惑。 (命名长度变量len可能有点反身,特别是如果你来自的语言没有任何问题。)

答案 1 :(得分:0)

  

为什么我会陷入僵局?我期待一个无限循环(因为我重新排队甚至生成更多元素的元素等等)。范围队列不应该总是在监听频道吗?

当通道上的范围循环到达通道缓冲区的末尾时(如果通道有一个),它将阻塞,等待通过通道发送更多元素。如果没有其他正在运行或可运行的goroutine,那么我们已陷入僵局。

最小例子:

http://play.golang.org/p/Vb4-RFEmm3

package main

func main() {

    queue := make(chan int)

    for _ = range queue {
    }
}
  

两个不同的goroutine是否将相同的元素排队?

没有。发生了什么事情就是这一行(26):

go func() { queue <- new }()

指示Go的运行时安排新的goroutine,它不一定立即执行。当goroutine有效运行时,new的值为1001,而不是打印的1000。