选择for循环死锁

时间:2016-06-03 13:54:44

标签: go

https://play.golang.org/p/FyIUPkwq0R

为什么以下死锁?

package main

import (
    "fmt"
)
var quit chan bool
var buffer chan string

func main() {
        buffer = make(chan string)
    quit = make(chan bool)
    go func() {
        i:=0
        for {
            select {
            case <- quit:
                fmt.Println("Bye!")
                return
            default:
                fmt.Println(<-buffer)
            }
            i++
            fmt.Println(i)
        }
    }()
    buffer <- "Go!"
    quit <- true        // This line dead locks
    //buffer <- "Hello" // When I do this instead it works?
    //quit <- true      // Also when I don't quit it still exit's?
}

3 个答案:

答案 0 :(得分:3)

此代码无法正常运行。你可能会很幸运,但显然你已经不幸了。你可能会很幸运,可能会发生以下情况:

让我们说我们有两个goroutines,A和B,其中A是运行main的goroutine,B是运行匿名函数的goroutine。可能会发生以下情况:

  • B:执行select;没有人在quit频道上撰稿,因此请执行default案例
  • B:执行<-buffer,所以开始阻止,等待有人写信给buffer
  • 答:写下来&#34; Go!&#34;到buffer
  • B:接收&#34; Go!&#34;然后打印出来。继续循环。
  • 答:对quit
  • 写真
  • B:执行select; A正在尝试写入quit,因此执行该案例。打印&#34;再见!&#34;并返回
  • 答:由于写完了,继续并从main
  • 返回

然而,这并不能保证会发生。特别是,在从buffer读取之后,B可能会继续执行,执行select,并在A有机会写入default之前落入quit案例。那可能发生了什么,看起来像这样:

  • B:执行select;没有人在quit频道上撰稿,因此请执行default案例
  • B:执行<-buffer,所以开始阻止,等待有人写信给buffer
  • 答:写下来&#34; Go!&#34;到buffer
  • B:接收&#34; Go!&#34;然后打印出来。继续循环
  • B:执行select;没有人在quit频道上撰稿,因此请执行default案例
  • B:执行<-buffer,所以开始阻止,等待有人写信给buffer
  • 答:对quit写真,所以开始阻止,等待某人阅读quit

现在A和B都被阻止,并且由于系统中没有其他goroutine,因此任何事件都无法解除阻塞,因此系统卡住了。

解决方案

解决这个问题的一种方法是使goroutine B从buffer读取select个案例而不是选择案例。这样,select将一直阻塞,直到任一通道可用于某个操作,并且您的代码将按照您可能想要的那样运行:

select {
    case <-quit:
        fmt.Println("Bye!")
        return
    case str := <-buffer:
        fmt.Println(str)
}

在Go Playground上查看here

但请注意,由于main goroutine在写入quit频道后立即返回,并且整个Go程序在发生这种情况时立即退出,您可能(也可能会) )不幸的是,fmt.Println("Bye!")在程序退出之前不会执行。

答案 1 :(得分:1)

将select default子句更改为

        default:
            fmt.Println("waiting on <-buffer")
            fmt.Println(<-buffer)

看看发生了什么。

问题是goroutine在main执行quit <- true之前执行选择中的默认分支。

goroutine阻挡了fmt.Println(<-buffer)main功能块quit <- true

为了防止死锁,请在case语句中接收:

        select {
        case <-quit:
            fmt.Println("Bye!")
            return
        case msg := <-buffer:
            fmt.Println(msg)
        }

playground example

答案 2 :(得分:0)

package main

import (
    "fmt"
)

var quit chan bool
var buffer chan string

func main() {
    buffer = make(chan string)
    quit = make(chan bool)

    go func() {
        i := 0
        for {
            select {
            case <-quit:
                fmt.Println("Bye!")
                return
            case v := <-buffer:
                fmt.Println(v)
            default:
                // 这里也可能 block
                // fmt.Println(<-buffer)
            }

            i++
            fmt.Println(i)
        }
    }()

    buffer <- "Go!"
    quit <- true // This line dead locks
    //buffer <- "Hello" // When I do this instead it works?
    //quit <- true      // Also when I don't quit it still exit's?
}