【Go】通过for range 读取channel

314 阅读1分钟

for range channel:仅当channel已被关闭且缓冲区内没有数据后,才会退出

测试阻塞及死锁:
当我们在main goroutine中make一个channel,通过开启一个发送方goroutine往channel里面写入数据,如下:

c1 := make(chan int, 100)
go func() {
   for i := 0; i < 10; i++ {
      c1 <- i
   }
   fmt.Println("c1 发送结束,但并未手动关闭")
   time.Sleep(time.Second * 3)
   fmt.Println("c1 goroutine 结束休眠")
   // 当前goroutine结束
}()

上面的例子中,等最后一个打印语句结束后,发送方goroutine结束;
但接收方goroutine仍在以for range方式读取channel,由于发送方goroutine已经结束,channel不再可能被关闭,也不会再发送消息,接收方goroutine for range循环被阻塞,不再可能执行 defer语w.Done();
则main goroutine w.Wait()语句不再等待,报错死锁结束main goroutine。

go func() {
   defer w.Done() // c1 未被关闭,不会跳出range,导致这一行永远阻塞,直到main goroutine结束
   for v := range c1 {
      fmt.Println("读取到:", v)
   }
   fmt.Println("退出了for range...") // 无法执行
}()
w.Wait() //发生死锁
fmt.Println("main done")

测试代码:

func blockTest() {
   var w sync.WaitGroup
   w.Add(1)
   c1 := make(chan int, 100)
   go func() {
      for i := 0; i < 10; i++ {
         c1 <- i
      }
      //close(c1)
      //fmt.Println("c1 closed...")
      fmt.Println("c1 发送结束,但并未手动关闭")
      time.Sleep(time.Second * 3)
      fmt.Println("c1 goroutine 结束休眠")
   }()
   go func() {
      defer w.Done()      // c1 未被关闭,不会跳出range,导致这一行永远阻塞,直到main goroutine结束
      for v := range c1 { // 阻塞
         fmt.Println("读取到:", v)
      }
      fmt.Println("退出了for range...")
   }()
   w.Wait() // 报错死锁
   fmt.Println("main done")
}