Channel 是能夠讓 goroutines 之間交換資料的機制。 使用 Channel 之前,有兩件事情要先說明,第一是使用 Channel 交換的資料一定有一個型別稱為 element type,第二個則是要有 發送端 以及 接收端

寫資料到 Channel

請參考下方的三點說明與程式碼,介紹如何寫值到 channel

  1. 使用 make宣告一個 int 型別的 channel c := make(chan int)
  2. 將 x 的值,寫入到 channel c <- x
  3. 使用 close 關閉 channel , 這表示 channel 將不能再被寫入
Write To Channel
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"time"
)

func main() {
c := make(chan int)
go writeToChannel(c, 10)
time.Sleep(1 * time.Second)
}

func writeToChannel(c chan int, x int) {
fmt.Printf("First: %d", x)
c <- x
close(c)
fmt.Printf("Second: %d", x)
}

線上範例: https://play.golang.org/p/WAZhKW86_2i

如果讀者有執行上方程式碼,會發現一件事情,就是只有 First: 10 被列印出來, Second: 10 沒有列印出來。

這是因為我們使用 c := make(chan int) 宣告 channel ,這表示該 channel 沒有任何暫存區 (Buffered Channels),所以 channel block c <- x 之後的程式碼,會等待 channel 的值被讀取後才會往下執行。

從 Channel 讀取資料

請參考下程式碼,說明如何讀取 channel 的值,而這段程式碼只有一個重點就是,只要把 <- 的位置對調,就可以從 channel 讀值了。

Reading from a channel
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
c := make(chan int)
go writeToChannel(c, 10)
time.Sleep(1 * time.Second)
v := <- c
fmt.Println("Read:", v)
time.Sleep(1 * time.Second)
}

func writeToChannel(c chan int, x int) {
fmt.Println("First:", x)
c <- x
close(c)
fmt.Println("Second:", x)
}

線上範例: https://play.golang.org/p/TVzsPsJwBAq

有執行上方程式碼的會發現,輸出結果的順序是

1
2
3
First: 10
Read: 10
Second: 10

這如 從 Channel 讀取資料 段落所說明的,當讀取完值之後,writeToChannel 之後的程式碼才會往下執行。

讀者可以把 channel 改成有暫存區的 channel,只要在 make chan 後面加上 buffer 的數量 => c := make(chan int, 1)。 而根據 Buffered Channels 的說明,當 buffer 滿了之後,才會 block 程式碼。 所以改成有暫存區的 channel 之後,因為有 buffer 的關係,程式碼不會 block ,會產生下方順序的輸出。

First: 10
Second: 10
Read: 10

取得 channel 開關的狀態

如下方的程式碼,在取得值的時候,只要宣告第二個參數,就可以得到 channel 是否被 close 的狀態。

Get channel status
  • go
1
2
3
4
5
6
v, ok := <-c
if ok {
fmt.Println("Channel is open!")
} else {
fmt.Println("Channel is closed!")
}

讀取 channel 的次數大於寫入 channel 的次數

這段落會分成兩種情形來說明,channel 尚未關閉的情形,以及 channel 關閉後的情況。

Channel 尚未關閉

如下方程式碼,只有寫入兩次的值到 channel,但是讀取了三次。

Without close channel
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

func main() {
c := make(chan int, 10)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}

程式範例: https://play.golang.org/p/7wP6R3oo-SS
上方的程式碼會出現下方的輸出, deadlock
1
2
3
4
5
6
7
8
1
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
D:/Go_Labs/channel_lab/main.go:15 +0x27c
exit status 2

這是因為,第三個接收訊息的程式碼 fmt.Println(<-c) ,一直在等待第三個訊息出現,可是程式碼只有寫入兩次訊息,所以造成 deadlock。

Channel 已關閉

這次我在讀取 channel 之前,先把 channel 關閉,如下方程式碼。

Close channel
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func main() {
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}

範例程式: https://play.golang.org/p/z4kRgs1oHq9
上方的程式碼會出現下方的輸出,第三次讀取 channel 的值是 0
1
2
3
1
2
0

這是因為,當 channel 關閉之後,如果讀取次數超過寫入次數,channel 會回傳該型別的預設值,而 int 的預設值就是 0 。

限制 channel 讀取與寫入

Channel 可以在 function 傳入的參數指定此 function 只能對 channel 寫入或讀取。

如下方程式碼的兩個 functions ,writeOnlyreadOnly,在 function 傳入參數的地方有指定此 function 只能夠有讀取權限或是寫入權限。

Read Write only
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

func main() {
c := make(chan int, 1)
writeOnly(c)
readOnly(c)
}

func writeOnly(out chan<- int) {
out <- 10
}

func readOnly(in <-chan int) {
fmt.Println("Read:", <-in)
}

如果在只有讀取權限的 function 裡面寫入 channel 的話,則 GO 編譯會出現錯誤,反之亦然。 錯誤訊息如下方範例:

1
2
3
invalid operation: range in (receive from send-only type chan<- int)

invalid operation: out <- i (send to receive-only type <-chan int)

最後要注意的是,close 只能用在有寫入權限的 channel,如果用在只有讀取權限的 channel ,則會造成編譯錯誤 invalid operation: close(out) (cannot close receive-only channel)

資料來源

[Mastering Go - Second Edition]