为了账号安全,请及时绑定邮箱和手机立即绑定

在 for 循环死锁中选择

在 for 循环死锁中选择

Go
噜噜哒 2022-01-17 10:40:38
https://play.golang.org/p/FyIUPkwq0R为什么会出现下面的死锁?package mainimport (    "fmt")var quit chan boolvar buffer chan stringfunc 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 回答

?
慕勒3428872

TA贡献1848条经验 获得超6个赞

不保证此代码可以正常工作。你可能会很幸运,但显然你一直很不走运。您可能会很幸运,并且可能会发生以下情况:

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

  • B:执行select;没有人在quit频道上写,所以执行default此案

  • B:执行<-buffer,所以开始阻塞,等待有人写信buffer

  • 答:写“走!” 到buffer

  • B:收到“Go!” 并打印出来。继续循环。

  • A:写真实的 quit

  • B:执行select;A 正在尝试写入quit,因此请执行该案例。打印“再见!” 并返回

  • A: 既然写完了,继续从 main

但是,这不保证会发生。特别是,在读取 之后buffer,B 可能会继续执行,执行,并在 A 有机会写入之前select陷入这种情况。这就是可能发生的事情,看起来像这样:defaultquit

  • B:执行select;没有人在quit频道上写,所以执行default此案

  • B:执行<-buffer,所以开始阻塞,等待有人写信buffer

  • 答:写“走!” 到buffer

  • B:收到“Go!” 并打印出来。继续循环

  • B:执行select;没有人在quit频道上写,所以执行default此案

  • B:执行<-buffer,所以开始阻塞,等待有人写信buffer

  • A: 写 true to quit,所以开始阻塞,等待有人读取quit

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

解决方案

解决此问题的一种方法是使 goroutine B 从buffer作为其中一种select情况而不是在选择情况中读取。这样,select将简单地阻塞,直到任一通道可用于操作,并且您的代码将按照您可能希望的方式运行:

select {

    case <-quit:

        fmt.Println("Bye!")

        return

    case str := <-buffer:

        fmt.Println(str)

}

在 Go Playground 上看到它。


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


查看完整回答
反对 回复 2022-01-17
?
白衣染霜花

TA贡献1796条经验 获得超10个赞

将选择默认子句更改为


        default:

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

            fmt.Println(<-buffer)

看看发生了什么。


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


goroutine 阻塞 at fmt.Println(<-buffer),main函数阻塞 at quit <- true。


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


        select {

        case <-quit:

            fmt.Println("Bye!")

            return

        case msg := <-buffer:

            fmt.Println(msg)

        }


查看完整回答
反对 回复 2022-01-17
?
SMILET

TA贡献1796条经验 获得超4个赞

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?

}


查看完整回答
反对 回复 2022-01-17
  • 3 回答
  • 0 关注
  • 157 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信