关于Range用法的一点想法

For every complex problem there is an answer that is clear , simple , and wrong.
任何复杂的问题,都有一个简单的错误答案。
----H.L.Mencken


  今天在复习管道的用法时,想实现两个协程,一个协程负责向管道中写入10个数字,另一个协程负责从管道中读出这10个数字,就是下面这个程序。

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup


func main(){
    wg.Add(2)
    ch := make(chan int , 10)
    go SetChan(ch)
    go LoadChan(ch)
    wg.Wait()
}

// 写入数值
func SetChan(ch chan <- int){
    for i := 0 ; i < 10 ; i++{
        ch <- i
    }
        close(ch)
    defer wg.Done()
}

// 负责从ch中读取数值
func LoadChan(ch <- chan int){
    for v := range ch {
        fmt.Println(v)
    }
    defer wg.Done()
}
// all goroutines are asleep - deadlock!

  然后,编译器果然无情地报错:"all goroutines are asleep - deadlock!"这让我意识到,可能是range这个方法读取channel的时候并不是读取完成之后,就立即退出,而是陷入阻塞,期待channel中再次被写入数据,所以并不会执行wg.Done(),而主协程的wg.Wait()一直等不到该协程执行wg.Done(),然后程序陷入死锁。
  为了验证我的想法,重新设计LoadChan()函数,在其中加入一个判断,如果读取完成,就使用break停止,重写后的LoadChan()函数如下所示:

func LoadChan(ch <- chan int){
     // 等待setChan协程写完数据
    time.Sleep(time.Second)
    for v := range ch {
        fmt.Println(v)
        // 如果读取完成,就停止
        if len(ch) == 0{
            break
        }
    }
    defer wg.Done()
}

  运行程序,果然没有死锁的情况发生,说明猜想正确,即range读取完成channel中的数据之后,并不会主动退出,而是陷入阻塞。如果不主动停止,就会使程序产生死锁。
  这也解释了为什么书中在讲到使用range遍历channel的时候,一定要先close(channel)。否则就会产生死锁的情况。

你可能感兴趣的:(关于Range用法的一点想法)