Go - channel

語言: CN / TW / HK

channel

一個 channels 是一個通訊機制,是goroutine之間的通訊機制

Go語言提倡使用通訊的方法代替共享記憶體,當一個資源需要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,並提供了確保同步交換資料的機制。宣告通道時,需要指定將要被共享的資料的型別。可以通過通道共享內建型別、命名型別、結構型別和引用型別的值或者指標。

在任何時候,同時只能有一個 goroutine 訪問通道進行傳送和獲取資料,總是遵循先入先出(First In First Out)的規則,保證收發資料的順序

宣告通道型別

var c chan int

chan 型別的空值是 nil,聲明後需要配合 make 後才能使用

建立通道

c1 := make(chan int)        //整型型別通道
c2 := make(chan interface{}) //可以放任意型別
c3 := make(chan * Struct)   //指標通道

通道發資料

  1. 把資料往通道中傳送時,如果接收方一直都沒有接收,那麼傳送操作將持續阻塞。Go 程式執行時能智慧地發現一些永遠無法傳送成功的語句並做出提示
c1 <- 1
c2 <- "xx"

通道接收資料

  1. 由於通道的資料在沒有接收方處理時,資料傳送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行
  2. 如果接收方接收時,通道中沒有傳送方傳送資料,接收方也會發生阻塞,直到傳送方傳送資料為止
  3. 通道一次只能接收一個數據元素
//阻塞接收資料
a:= <- c1

//非阻塞接收資料
data,ok:=<- c2

//忽略接收的資料,執行該語句時將會發生阻塞,直到接收到資料,但接收到的資料會被忽略
<- c2

//迴圈接收

for data := range ch {
}

非阻塞的通道接收方法可能造成高的 CPU 佔用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel 進行

示例:


var (
	ch = make(chan int)
)
func main() {
	fmt.Println("開始生產資料")
	go task()
	for i := 1; i < 10; i++ {
		ch <- i
	}
	fmt.Println("我沒有資料了")
	ch <- 0
	<-ch
	fmt.Println("生產結束")
}

func task() {
	for {
		data := <-ch
		fmt.Printf("消費了資料%d\n", data)
		if data == 0 {
			fmt.Println("我收到結束資訊了")
			break
		}
	}
	ch <- 0
}

單向通道的宣告

ch := make(chan int)

// 宣告一個只能寫入資料的通道型別, 並賦值為ch
var c1 chan <- int = ch

//宣告一個只能讀取資料的通道型別, 並賦值為ch
var c2 <- chang int = ch

關閉 channel

close(ch)

//ok 可以驗證通道已經關閉了
x, ok := <-ch

示例:


var (
	ch = make(chan int)
)
func main() {
	go product()
	for {
		data, ok := <-ch
		if !ok {
			break
		}
		fmt.Println(data)
	}
	fmt.Println("程式結束")
}
func product() {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
	fmt.Println("生產結束")
}

無緩衝通道

Go語言中無緩衝的通道(unbuffered channel)是指在接收前沒有能力儲存任何值的通道。這種型別的通道要求傳送 goroutine 和接收 goroutine 同時準備好,才能完成傳送和接收操作

緩衝通道

Go語言中有緩衝的通道(buffered channel)是一種在被接收前能儲存一個或者多個值的通道。這種型別的通道並不強制要求 goroutine 之間必須同時完成傳送和接收。通道會阻塞傳送和接收動作的條件也會不同。只有在通道中沒有要接收的值時,接收動作才會阻塞。只有在通道沒有可用緩衝區容納被髮送的值時,傳送動作才會阻塞。

  // 建立一個3個元素緩衝大小的整型通道
 ch := make(chan int, 3)
 
  // 檢視當前通道的大小
  fmt.Println(len(ch))
    
    

通道超時

可以使用 select 來設定超時

超時機制本身雖然也會帶來一些問題,比如在執行比較快的機器或者高速的網路上執行正常的程式,到了慢速的機器或者網路上執行就會出問題,從而出現結果不一致的現象,但從根本上來說,解決死鎖問題的價值要遠大於所帶來的問題。

select 有比較多的限制,其中最大的一條限制就是每個 case 語句裡必須是一個 IO 操作

  • 如果其中的任意一語句可以繼續執行(即沒有被阻塞),那麼就從那些可以執行的語句中任意選擇一條來使用。

  • 如果沒有任意一條語句可以執行(即所有的通道都被阻塞),那麼有如下兩種可能的情況:

    1. 如果給出了 default 語句,那麼就會執行 default 語句,同時程式的執行會從 select 語句後的語句中恢復;
    2. 如果沒有 default 語句,那麼 select 語句將被阻塞,直到至少有一個通訊可以進行下去。
for {
            select {
            case num := <-ch:
                fmt.Println("num = ", num)
            case <-time.After(3 * time.Second):
                fmt.Println("超時")
                quit <- true
            }
        }