📢 本文由 gemini-2.5-flash 翻譯
Golang 系列
Hello GoLang:
https://blog.yexca.net/archives/154
GoLang (var and const) 變數與常數:
https://blog.yexca.net/archives/155
GoLang (func) 函式:
https://blog.yexca.net/archives/156
GoLang (slice and map) 切片:
https://blog.yexca.net/archives/160
GoLang (OOP) 物件導向:
https://blog.yexca.net/archives/162
GoLang (reflect) 反射:
https://blog.yexca.net/archives/204
GoLang (struct tag) 結構體標籤:
https://blog.yexca.net/archives/205
GoLang (goroutine) Go 協程:
https://blog.yexca.net/archives/206
GoLang (channel) 通道: 本文
Go 協程 (goroutine) 可以透過通道 (channel) 傳遞資料,引用型別的通道可用於多個 Go 協程之間的通訊,其內部實作了同步機制,確保併發安全
有點類似
RabbitMQ
(僅個人為方便學習所類比,實則為不同東西)
定義變數
通道為引用型別,複製或函式呼叫時將引用同一個通道物件,零值為 nil
透過 make() 函式建立,例如
1
2
3
| c := make(chan int)
// 新增容量為 3
c := make(chan int, 3)
|
當容量為 0 時,通道是無緩衝區、會阻塞讀寫的;大於 0 時有緩衝區、非阻塞,直到寫滿才會阻塞
透過 <- 來接收和傳送資料
1
2
3
4
5
6
7
8
| // 傳送資料到通道
channel <- 3
// 接收並捨棄
<-channel // 注意無空格
// 接收並賦值給變數
x := <-channel
// 接收並賦值給變數,並判斷是否接收成功(通道是否為空)
data, flag := <-channel
|
無緩衝區
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import "fmt"
func main() {
c := make(chan int)
go func() {
defer fmt.Println("A.defer")
c <- 6
fmt.Println("A 正在執行")
}()
num := <-c
fmt.Println("num =", num)
fmt.Println("main 結束")
}
|
有緩衝區
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3)
go func() {
defer fmt.Println("A 結束")
for i := 0; i < 3; i++ {
c <- i
fmt.Println("A Go 協程, i =", i, "len =", len(c), "cap =", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("main, num =", num)
}
fmt.Println("main 結束")
}
|
關閉通道
透過 close() 關閉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| package main
import "fmt"
func main() {
c := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// 關閉
close(c)
}()
for {
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Main 完成")
}
|
使用 range
上述 main 的 for 迴圈可以簡寫使用 range
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package main
import "fmt"
func main() {
c := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
//for {
// if data, ok := <-c; ok {
// fmt.Println(data)
// } else {
// break
// }
//}
for data := range c {
fmt.Println(data)
}
fmt.Println("Main 完成")
}
|
單向通道
預設情況下,通道是雙向的,即可讀可寫,也可以指定通道方向,只讀或只寫
1
2
3
4
5
| var c chan int // 宣告正常雙向通道
// c1 只可寫
var c1 chan<- int
// c2 只可讀
var c2 <-chan int
|
可以把雙向通道轉為單向,反之則不行。也就是可以定義函式形參為單向,但傳遞雙向通道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // chan<- // 只寫
func counter(out chan<- int) {
defer close(out)
for i := 0; i < 5; i++ {
out <- i // 如果對方不讀,會阻塞
}
}
// <-chan // 只讀
func printer(in <-chan int) {
for num := range in {
fmt.Println(num)
}
}
func main() {
c := make(chan int) // 雙向
go counter(c) // 生產者
printer(c) // 消費者
fmt.Println("完成")
}
|
select
select 可以監聽多個通道上的資料流動,語法與 switch 類似,但每個 case 敘述中必須是一個 I/O 操作
一般放到 for{} 敘述區塊中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package main
import "fmt"
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
// main
x, y := 1, 1
for {
select {
case c <- x:
tmp := x
x = y
y = tmp + y
case <-quit:
fmt.Println("退出")
return
// 可以有 default,此範例不需要
}
}
}
|