go通道channel使用for-range造成死锁,而使用for计数器迭代却不会?

使用以下代码可以正常执行

func main() {
    count := 10
    ch := generate(count)
    for i := 0; i < 10; i++ {
        fmt.Println(<-ch)
    }
}
func generate(count int) <-chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < count; i++ {
            ch <- i
        }
    }()
    return ch
}

但是换成以下代码,却会提示 fatal error: all goroutines are asleep - deadlock!

func main() {
    count := 10
    ch := generate(count)
    for v := range ch {
        fmt.Println(v)
    }
}
func generate(count int) <-chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < count; i++ {
            ch <- i
        }
    }()
    return ch
}

想知道使用上述代码时,for-range循环接收造成死锁的原因是什么?这两者的区别是什么?

阅读 9.8k
4 个回答

RangeClause

For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel is nil, the range expression blocks forever.

只有 channel 被关掉才能结束。

你说的这个我也在《The Way to Go》 也有看到,但是不理解“从指定通道中读取数据直到通道关闭”中的直到通道关闭是指什么。channel关闭是指执行close(ch)方法吗?但返回的是一个只读channel,不可以关闭。

是的,close(ch)。关闭之后,这个 channel 无法发送(会 panic),也无法接受(缓存用光后,直接返回 zero value)。

在generate返回ch前关闭,虽然没有报死锁,但是没有打印任何数据。

这个估计 go routine 启动之前 ch 就关闭了, range 一个数据都读不到直接退出,main 结束,程序退出。

程序如果没有很快退出,go routine 执行了,向关闭的 ch 写数据会 panic 。

而且,还是用for-range读取通道数据,但是用一个新goroutine却不会发生死锁

   go func() {
       for v := range ch {
           fmt.Println(v)
       }
   }()

看一眼你的错误:

fatal error: all goroutines are asleep - deadlock!

只有所有的 go routine 全部锁住才会报死锁。但是,你再起一个新的 go routine ,你的发送跟接受的 go routine 都锁住了,但是 main 所在的 go routine 并没有被锁住,所以不会报此错误。

for range是阻塞式读取channel,只有channel close之后才会结束

只要没有goroutine持有channel,相关资源会自动释放, channel不需要通过close释放资源。

新手上路,请多包涵

楼主你明白了么,反正我是明白了。

package main

import (
    "fmt"
)
/**
个人理解:
    前提:无缓冲通道,没有容量,读写是阻塞的。意思就是写一个,必须读一个,否则写就会一直阻塞。
    for 为啥不会出错,因为手动的的控制了读取的次数。写10次。读10次。
        如果main for 里多读取一次还是会报错,比如(i<=10),因为通道没有值了,再读取就会一直阻塞,报死锁错误。
    for range 为啥报错那,是因为没有close(ch),已经没有值了。还读取就会一直阻塞,程序就会报死锁。
        手动的 close(ch) 掉后,range 在读完chan里的值后会自动结束循环。

    再者就是 为什么close(ch) 位置不一样,导致结果不一样。这里下面解释已经很清楚了。
        这里关键点是要知道阻塞读写的概念,写一个、读一个
**/
func main() {
    count := 10
    ch := generate(count)

    // 阻塞读,人为控制读取次数。
    //for i := 0; i <= 10; i++ {
    //    fmt.Println("main for 取值 ",i)
    //    fmt.Println(<-ch)
    //    fmt.Println("main for 等待",i)
    //}

    /**
        也是阻塞读。人为不干预,只有close(ch)后,for range才会自动停止。
        否则会一直读取,通道里没有值了。还继续读取就会阻塞,程序就会报死锁错误。
    **/

    for v := range ch {
        fmt.Println("main for_range 取值")
        fmt.Println(v)
        fmt.Println("main for_range 等待")
    }

    // 延伸测试:
    //   这里是为了等待 generate函数中的 go func  执行完,更直观看出结果
    //for{}
}

func generate(count int) <-chan int {
    ch := make(chan int)
    go func() {
        // 因为 ch 是无缓冲通道,所以是阻塞的,也就是 这里 for循环写一个,main函数里面 for / for range 读一个。
        for i := 1; i < count; i++ {
            fmt.Println("generate 往通道放值",i)
            ch <- i
            fmt.Println("generate 函数等待",i)
        }
        /**
            上面10次读写完后。关闭ch,main函数的 for range 就会自动结束循环。
            其实前10次一直都会读写阻塞的,直到这里关闭后。main 里面的for range 才会自动结束循环。
            总结:这里close(chan)为啥能打印值,是因为上面阻塞的执行完后,程序才执行到这里。
         **/
        fmt.Println("上面写完了,执行关闭了")
        close(ch)
    }()

    /**
        为什么这里关闭后。main函数中for range读不到数据?
           关闭后的 chan,for range 会自动结束循环.
        for 循环更能看到效果,这里有个概念要知道,往已关闭的chan里写数据会报错。
        但是读一个关闭的chan不会报错,这里会读取到默认值:0

        延伸:如果main函数 最后 加上 for{},就会发现这里关闭后会报 panic: send on closed channel
             因为这里已经close(chan),但是上面的匿名函数还在往 chan 里写数据。
     **/
    //fmt.Println("这里执行了关闭操作")
    //close(ch)
    return ch
}

你遇到的问题是因为没有 close(ch),改成如下方式就好了

go func() {
    for i := 0; i < count; i++ {
        ch <- i
    }
    // 这里生产者不再生产数据了就把ch关闭 这样来通知消费者,没数据了不用再等待了
    close(ch)
}()

golang中的channel思路就是生产者消费者,无论生产者写入数据还是消费者读取数据都是阻塞的,理解这个的思路要基于阻塞这个前提。

fori这种形式是自己判断从channel中读取多少次
for range 这种就是是runtime帮我们来判断了,他的判断标准是 close(ch)

你的fori改成多一次循环同样会被go判定为 deadlock
因为最后一次的读取会一直阻塞在那里,原因是生产者不再生产了,消费者还阻塞在那里等待。go判断到这个会一直阻塞在这里的场景就直接抛出错误退出了,否则这个进程就一直hang在这里还不易被发现。

导致这种错误的情况有两种
生产者
没有消费者消费channel中的数据,channel中的数据已经填充满了,但是还在往里写入,此刻是要阻塞等待的,由于没有消费者,这个阻塞会一直阻塞下去
消费者
生产者不再生产数据了,也就是是 channel 中会一直为空了,但是消费者还在读取channel中的数据,这个读取也是阻塞等待的,channel中不会再有数据,这个等待也是会一直等待下去

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏