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 必須是可指派給 channel ch 元素類型的值
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 操作
  • ChannelsGoroutines 配合成為管理同步與並發的強大工具

select vs switch

selectswitch
可阻塞(用於 channels)非阻塞
非確定性(隨機選擇 case)確定性(依序選擇匹配的 case)

相關主題