我自己的理解, channel 簡單來說就是資料傳遞的緩存區,用圖片會比較容易理解,以下圖片截至 參考來源2 ,可以看到 channel 就是兩個 goroutine 間傳遞資訊的通道
我們先用下面一段程式碼來介紹一下 channel
// 情況 1
func chanDemo1() {
// chan type 是 channel 的資料型態
// 可以是 chan int 也可以是 chan string 等等
c := make(chan int)
c <- 1
c <- 2
n := <-c
fmt.Println(n)
}
func main() {
chanDemo1()
// chanDemo2()
// chanDemo3()
// 多下面這行等待時間是要避免 goroutine 間的資料還沒傳遞完
// 程式碼就已經結束運行
time.Sleep(time.Millisecond)
}
輸出結果為:
fatal error: all goroutines are asleep - deadlock!
結果發生 deadlock ,參考來源4 有講解為什麼會發生這個問題,主要是因為如果只有單純宣告 channel,並沒有初始化要給該 channel 多少容量的話,元素會放不進去,放不進去的話這邊就會一直被 block 住,因此有以下兩種做法,一是開好固定的 channel 數量,第二種是搭配 goroutine
情況 1:開好固定數量的 channel,以上面的例子,因為會塞兩個東西進到 c 裡面,因此給他至少 2 個空間就不會報錯
// func chanDemo() {
// // chan type 是 channel 的資料型態
// // 可以是 chan int 也可以是 chan string 等等
c := make(chan int, 2)
// c <- 1
// c <- 2
// n := <-c
// fmt.Println(n)
n := <-c
fmt.Println(n)
// }
// func main() {
// chanDemo1()
// chanDemo2()
// chanDemo3()
// chanDemo4()
// // 多下面這行等待時間是要避免 goroutine 間的資料還沒傳遞完
// // 程式碼就已經結束運行
// time.Sleep(time.Millisecond)
// }
輸出結果為:
1
2
因為 channel 是採取先進先出,因此 n := <- c 這行只取了一次,那麼因為我們先放進 1 ,因此會取到 1,如果想要取到 2 ,那就在下面多一行 n = <- c 就可以取到 2 ,另一個情況就不需要先定義空間大小,是透過 channel 搭配 goroutine 一起使用
情況 2:channel 搭配 goroutine
// 情況 2
func chanDemo2() {
// chan type 是 channel 的資料型態
// 可以是 chan int 也可以是 chan string 等等
c := make(chan int)
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
}
輸出結果為:
1
2
那還有另一種常見寫法:
func makeChan3() chan int {
c := make(chan int)
go func() {
for {
fmt.Println(<-c)
}
}()
return c
}
func chanDemo3() {
var channels [10]chan int
for i := 0; i < 10; i++ {
channels[i] = makeChan3()
}
for i := 0; i < 10; i++ {
channels[i] <- i
}
}
輸出的結果為,因為同時開創了 10 個 channel,因此誰先誰後就不一定了,所以順序才會是亂的,但能確保的是一定都會有,除非程式碼提前結束:
0
5
2
3
4
7
1
6
8
9
最上面的示意圖有提到 channel 可以收發資料,但如果想要特別定義是收資料或發資料的話,可以如下定義,以下用收資料為例:
// func chanDemo4() {
// 此為 channel 純收資料
// 如果想要變成 channel 純發資料,則寫成 <-chan int
var channels [10]chan<- int
// for i := 0; i < 10; i++ {
// channels[i] = makeChan4()
// }
// for i := 0; i < 10; i++ {
// channels[i] <- i
// }
//}
func makeChan4() chan<- int {
// c := make(chan int)
// go func() {
// for {
// fmt.Println(<-c)
// }
// }()
// return c
//}
情況 1 與情況 2 乍看之下會覺得情況 2 比較方便,不過情況 1 對於提升效能會比情況 2 有優勢,因此可以斟酌使用,但是根據 參考來源1 ,還有更好的做法,原因是以上的幾種都需要執行 time.Sleep(time.Millisecond) 才能確保資料收齊,但是有沒有辦法透過給與標籤來確認收發的內容中已夾帶全部的訊息呢?詳情請繼續看下去
https://github.com/luckyuho/ithome30-golang/tree/main/day13