Go語言關閉通道後繼續使用通道

2020-07-16 10:05:22
通道是一個參照物件,和 map 類似。map 在沒有任何外部參照時,Go語言程式在執行時(runtime)會自動對記憶體進行垃圾回收(Garbage Collection, GC)。類似的,通道也可以被垃圾回收,但是通道也可以被主動關閉。

格式

使用 close() 來關閉一個通道:

close(ch)

關閉的通道依然可以被存取,存取被關閉的通道將會發生一些問題。

給被關閉通道傳送資料將會觸發 panic

被關閉的通道不會被置為 nil。如果嘗試對已經關閉的通道進行傳送,將會觸發宕機,程式碼如下:
package main

import "fmt"

func main() {
    // 建立一個整型的通道
    ch := make(chan int)

    // 關閉通道
    close(ch)

    // 列印通道的指標, 容量和長度
    fmt.Printf("ptr:%p cap:%d len:%dn", ch, cap(ch), len(ch))

    // 給關閉的通道傳送資料
    ch <- 1
}
程式碼執行後觸發宕機:

panic: send on closed channel

程式碼說明如下:
  • 第 7 行,建立一個整型通道。
  • 第 10 行,關閉通道,注意 ch 不會被 close 設定為 nil,依然可以被存取。
  • 第 13 行,列印已經關閉通道的指標、容量和長度。
  • 第 16 行,嘗試給已經關閉的通道傳送資料。

提示觸發宕機的原因是給一個已經關閉的通道傳送資料。

從已關閉的通道接收資料時將不會發生阻塞

從已經關閉的通道接收資料或者正在接收資料時,將會接收到通道型別的零值,然後停止阻塞並返回。

操作關閉後的通道:
package main

import "fmt"

func main() {
    // 建立一個整型帶兩個緩衝的通道
    ch := make(chan int, 2)
   
    // 給通道放入兩個資料
    ch <- 0
    ch <- 1
   
    // 關閉緩衝
    close(ch)

    // 遍歷緩衝所有資料, 且多遍歷1個
    for i := 0; i < cap(ch)+1; i++ {
   
        // 從通道中取出資料
        v, ok := <-ch
       
        // 列印取出資料的狀態
        fmt.Println(v, ok)
    }
}
程式碼執行結果如下:

0 true
1 true
0 false

程式碼說明如下:
  • 第 7 行,建立一個能儲存兩個元素的帶緩衝的通道,型別為整型。
  • 第 10 行和第11行,給這個帶緩衝的通道放入兩個資料。這時,通道裝滿了。
  • 第 14 行,關閉通道。此時,帶緩衝通道的資料不會被釋放,通道也沒有消失。
  • 第 17 行,cap() 函數可以獲取一個物件的容量,這裡獲取的是帶緩衝通道的容量,也就是這個通道在 make 時的大小。雖然此時這個通道的元素個數和容量都是相同的,但是 cap 取出的並不是元素個數。這裡多遍歷一個元素,故意造成這個通道的超界存取。
  • 第 20 行,從已關閉的通道中獲取資料,取出的資料放在 v 變數中,型別為 int。ok 變數的結果表示資料是否獲取成功。
  • 第 23 行,將 v 和 ok 變數列印出來。

執行結果前兩行正確輸出帶緩衝通道的資料,表明緩衝通道在關閉後依然可以存取內部的資料。

執行結果第三行的“0 false”表示通道在關閉狀態下取出的值。0 表示這個通道的預設值,false 表示沒有獲取成功,因為此時通道已經空了。我們發現,在通道關閉後,即便通道沒有資料,在獲取時也不會發生阻塞,但此時取出資料會失敗。