1. 程式人生 > >從兩道題看go channel的用法

從兩道題看go channel的用法

art 不一定 解釋 second lose code span con range

在知乎看到有人分享了幾道筆試題,自己總結了一下其中與channel有關的題目。全部題目在這裏:

https://zhuanlan.zhihu.com/p/35058068

題目

5、請找出下面代碼的問題所在。

func main() {
    ch := make(chan int, 1000)
    // goroutine1
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
    }()
    
    // goroutine2
    go func() {
        for
{ a, ok := <-ch if !ok { fmt.Println("close") return } fmt.Println("a: ", a) } }() close(ch) fmt.Println("ok") time.Sleep(time.Second * 100) }

8、請說出下面代碼哪裏寫錯了

func main() {
    abc := make(chan
int, 1000) for i := 0; i < 10; i++ { abc <- i } // receiver goroutine go func() { for { a := <-abc fmt.Println("a: ", a) } }() close(abc) fmt.Println("close") time.Sleep(time.Second * 100) }

解釋

首先要明確這兩個要點:

  • 向一個已經關閉的channel發送數據是會拋panic的,但是從一個已關閉的channel接收數據並不會拋panic,而是得到channel數據類型的零值。
  • 同一個段程序每次啟動後goruntine的調度不一定相同,所以goroutine的執行順序有可能不一樣。

先看第一題,代碼運行時會拋出panic:

panic: send on closed channel

這是因為goroutine1 還沒把i 發送給ch,ch 就在main函數中被close,當把i 發送到ch 時就拋出了panic。同理goroutine2 中從已關閉的ch 中接收數據時ok 會返回false,然後就return了。而且由於goroutine 的執行順序不一樣,輸出close、ok、panic的順序也會不一樣,有興趣的話可以自己跑幾次。
那麽如果想讓程序正常輸出應該怎麽改呢,下面是其中一種方法:

func main() {
    ch := make(chan int, 1000)
    // goroutine1
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
        
        // 只有一個goroutine向ch發送數據,可以考慮在這個goroutine中關閉。
        close(ch)
    }()
    
    // goroutine2
    go func() {
        for {
            a, ok := <-ch
            if !ok {
                fmt.Println("close")
                return
            }
            fmt.Println("a: ", a)
        }
    }()
    
    fmt.Println("ok")
    time.Sleep(time.Second * 100)
}

再來看第二題,運行時輸出完0—9後會一直輸出0,直到sleep的時間結束。這是因為在把0—9發送給abc 後abc 就被關閉了,receiver goroutine 在接收完0—9後沒有跳出for 循環,而是一直從被關閉的abc 中接收數據,所以接收到的是abc 的零值——0。
有一種最簡單的辦法可以改正這個程序:

func main() {
    abc := make(chan int, 1000)
    for i := 0; i < 10; i++ {
        abc <- i
    }
    go func() {
        // 使用for-range,abc被關閉後會自動退出for循環。
        for a := range abc {
            fmt.Println("a: ", a)
        }
    }()
    close(abc)
    fmt.Println("close")
    time.Sleep(time.Second * 10)
}

總結

使用channel時,

  • 如果只有一個goroutine 向channel發送數據,可以在該goroutine 中close channel。當有多個goroutine 向channel 發送元素時close channel 的方法可以參考這篇文章:

    https://www.jianshu.com/p/d24dfbb33781

  • 用for-range 從channel 中接收數據代碼簡潔而且基本沒有副作用。

擴展閱讀

關於goroutine是如何調度的,可以參考這篇文章:

https://blog.csdn.net/liangzhiyang/article/details/52669851

References

http://colobu.com/2016/04/14/Golang-Channels/

從兩道題看go channel的用法