golang 无缓冲channel的阻塞问题

done:=make(chan struct{})
c:=make(chan int)
go func() {
    defer close(done)
    for{
        s,ok:=<-c
        if !ok{
            //close(c)
            return
        }
        println(s)
    }
}()
for i:=0;i<1 ;i++  {
    println("put")
    c<-i
}
println("main....")
//close(c)
<-done

clipboard.png
当for循环到max+1的时候会出现阻塞问题,为啥B区域的close可以关闭通道,而在A区域无法关闭

clipboard.png

阅读 4.3k
2 个回答

go func协程里的s,ok:=<-c在一个for死循环里,第一遍循环阻塞再这里,等待c信号量。
main协程的for循环打印出put,然后给go func这个协程发了一个c信号,协程收到这个信号,开始继续执行,if !ok即为false所以不会return,此时go func协程会打印出0。进入下个循环,依旧阻塞在等待c信号量。
而main协程走到了println("main....")所以能够打印出main...

  1. 此时如果没有B区域close(c),那么go func协程阻塞在了等待c信号量上,而main协程阻塞在了等待down信号量,造成了deadlock!,A区域的close(c) 没有机会走到,所以不会return,不会走到defer,更不会走到close(down)
  2. 此时如果B区域有了close(c) ,那么main协程暂时阻塞在等待down信号量,go func接受到了这个c信号量继续走,此时c已经close,所以if !ok为true,此时可以走到return,跳出for死循环,走到defer函数,走到close(down),发送一个down信号量给main,此时main接着走,完成整个流程。

两个地方都可以关闭 channel,只不过下面这处有条件语句

        s,ok:=<-c
        if !ok{
            //close(c)
            return
        }

条件 !ok 意味着 channel 已经关闭,此时再调用 close(c) 已无意义。
而 channel 没有关闭时,除非有数据到达,否则 s, ok := <-c 进入堵塞状态。

你可以改成这样,在没有数据时退出循环

    for {
        select {
        case s, ok := <-c:
            if ok {
                println(s)
                continue
            }
            break
        default:  // channel 中无数据
            break
        }
    }
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题