通道(Channel)是 goroutine 之間通訊的管道,「以通訊代替共享」的具體載體。

基本用法#

// 建立 channel
ch := make(chan int)        // 無緩衝
chBuf := make(chan int, 10) // 有緩衝,容量 10

// 傳送 / 接收
ch <- 42
value := <-ch

// 關閉
close(ch)

無緩衝 vs 有緩衝#

// 無緩衝:同步通訊
// 傳送者阻塞,直到有接收者
ch := make(chan int)

go func() {
    ch <- 1  // 阻塞,直到有人接收
}()

value := <-ch  // 接收後,傳送端才會繼續

// 有緩衝:非同步通訊
// 只在緩衝區滿時,傳送才阻塞
chBuf := make(chan int, 2)

chBuf <- 1  // 不阻塞
chBuf <- 2  // 不阻塞
// chBuf <- 3  // 阻塞,緩衝區已滿
  • 無緩衝:需要嚴格同步、握手語意時使用
  • 有緩衝:允許傳送端先行、降低等待時間

操作規則#

操作nil channel已關閉 channel正常 channel
傳送永久阻塞panic阻塞/成功
接收永久阻塞回傳零值阻塞/成功
關閉panicpanic成功
ch := make(chan int, 1)
ch <- 10
close(ch)

// 從已關閉的 channel 接收
v1, ok := <-ch  // v1=10, ok=true
v2, ok := <-ch  // v2=0, ok=false(channel 已空且已關閉)

// 關閉後繼續傳送會 panic
// ch <- 20  // panic: send on closed channel

單向 Channel#

// 只能傳送
func producer(out chan<- int) {
    for i := 0; i < 10; i++ {
        out <- i
    }
    close(out)
}

// 只能接收
func consumer(in <-chan int) {
    for value := range in {
        fmt.Println(value)
    }
}

func main() {
    ch := make(chan int)
    go producer(ch)  // 雙向 channel 可隱式轉成單向
    consumer(ch)
}

把方向寫進函式簽章,可以讓「誰負責 close」的責任在編譯期就明確。

select 語句#

select 用於同時監聽多個 channel 操作:

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        time.Sleep(time.Second)
        ch1 <- 1
    }()

    go func() {
        time.Sleep(time.Second * 2)
        ch2 <- 2
    }()

    // 等第一個就緒的 channel
    select {
    case v := <-ch1:
        fmt.Println("from ch1:", v)
    case v := <-ch2:
        fmt.Println("from ch2:", v)
    case <-time.After(time.Second * 3):
        fmt.Println("timeout")
    }
}
select 的進階用法
// 非阻塞操作
select {
case v := <-ch:
    fmt.Println("received:", v)
default:
    fmt.Println("no value available")
}

// 帶 timeout 的傳送
select {
case ch <- value:
    fmt.Println("sent")
case <-time.After(time.Second):
    fmt.Println("send timeout")
}

// 永久等待
select {}  // 永久阻塞