Channels
Go 語言 Channel:Goroutines 間的通訊機制。
Channel 概念
- FIFO:Queue 結構
- 接收與發送資料
- 內建的 Goroutines 通訊功能
當你需要 goroutine 回傳資料時,使用 Channels。
建立 Channel
make(chan val-type)Channel Buffering
預設情況下 channels 是無緩衝的
Unbuffered Channel
- 需要等到讀或寫都完成,main 才可以完整結束
- 無緩衝 channel 沒有儲存值的容量
- 發送者在無緩衝 channel 上發送值時會阻塞,直到接收者接收該值
- 通常用於 goroutines 之間的同步通訊
- 確保發送者和接收者都準備好後才交換值
func main(){
ch := make(chan int)
go func(){
ch <- 42
}()
fmt.Println(<-ch)
}Buffered Channel
- 讀寫必須在不同的 Goroutine 環境下,才不會被 block
- 有定義的容量來儲存值
- 發送者只在 channel 滿時阻塞
- 接收者只在 channel 空時阻塞
- 可解耦發送者和接收者的時機,提供一定程度的非同步性
func main(){
ch := make(chan int, 2)
ch <- 100
ch <- 10
result1 := <-ch
result2 := <-ch
}何時使用 Buffered 或 Unbuffered?
Buffered Channel 適用於:知道啟動了多少 goroutines、想限制 goroutines 數量、或想限制排隊的工作量。
Channel Operations
發送值
<-是 channel 發送運算符v必須是可指派給 channelch元素類型的值
ch <- v接收值
<-是 channel 接收運算符val是儲存從 channel 讀取資料的變數
val := <-ch關閉 Channel
- close 函式的參數必須是 channel 值
close(ch)- 若
ok為 true,表示 channel 開啟 - 若
ok為 false,表示 channel 已關閉且無更多值可接收
v, ok := <-ch查詢 Channel 容量
cap(ch)查詢 Channel 長度
len(ch)Deadlock、Panic、Resource Leak Cases
Channel Deadlock
// Case 1: 單 Goroutine 寫入無緩衝 Channel
func deadlock1() {
ch := make(chan int)
ch <- 1 // deadlock: 無人接收
}
// Case 2: 單 Goroutine 讀取無緩衝 Channel
func deadlock2() {
ch := make(chan int)
<-ch // deadlock: 無人發送
}
// Case 3: 循環依賴
func deadlock3() {
ch1, ch2 := make(chan int), make(chan int)
go func() {
ch1 <- 1 // 等待 ch2
<-ch2
}()
go func() {
ch2 <- 1 // 等待 ch1
<-ch1
}()
}
// Case 4: 緩衝區已滿
func deadlock4() {
ch := make(chan int, 1)
ch <- 1
ch <- 2 // deadlock: 緩衝區已滿
}
// Case 5: nil channel 操作
func deadlock5() {
var ch chan int
<-ch // deadlock: nil channel
}
// Case 6: select 全部阻塞
func deadlock6() {
ch := make(chan int)
select {
case <-ch:
case ch <- 1:
} // deadlock: 無 default
}
// Case 7: range 未關閉 channel
func deadlock7() {
ch := make(chan int)
for v := range ch { // deadlock: 未關閉
}
}
// Case 8: 互斥鎖誤用
func deadlock8() {
var mu sync.Mutex
ch := make(chan int)
mu.Lock()
ch <- 1 // deadlock: mutex 永遠不會解鎖
}Channel Panic
// Case 1: 關閉後寫入
func panic1() {
ch := make(chan int)
close(ch)
ch <- 1 // panic: 寫入已關閉的 channel
}
// Case 2: 重複關閉
func panic2() {
ch := make(chan int)
close(ch)
close(ch) // panic: 重複關閉
}
// Case 3: 向已關閉的 channel 發送值
func panic3() {
ch := make(chan int)
go func() {
close(ch)
}()
ch <- 1 // 可能 panic: 發送到已關閉的 channel
}
// Case 4: nil channel 關閉
func panic4() {
var ch chan int
close(ch) // panic: 關閉 nil channel
}
// Case 5: 在關閉的 channel 上使用 select 發送
func panic5() {
ch := make(chan int)
close(ch)
select {
case ch <- 1: // panic: 發送到已關閉的 channel
default:
}
}Resource Leak
// Case 1: Goroutine 洩漏
func leak1() {
ch := make(chan int)
go func() {
<-ch // goroutine 永遠阻塞
}()
}
// Case 2: 接收者比發送者多
func leak2() {
ch := make(chan int, 1)
ch <- 1
close(ch)
<-ch
<-ch // 不會 deadlock,但會接收到零值
}
// Case 3: Context 取消但未處理
func leak3() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan int)
go func() {
select {
case <-ctx.Done():
return
case ch <- 1:
}
}()
cancel() // goroutine 可能洩漏
}Select
- 讓你等待多個 channel 操作
- 與 Channels 和 Goroutines 配合成為管理同步與並發的強大工具
select vs switch
| select | switch |
|---|---|
| 可阻塞(用於 channels) | 非阻塞 |
| 非確定性(隨機選擇 case) | 確定性(依序選擇匹配的 case) |