介紹
channel 在 Go 中是一種專門用來在 goroutine 之間傳遞數據的類型安全的管道。
你可以把它理解成:
- 多個 goroutine 之間的**“傳話筒”**,誰往通道里塞東西,另一個 goroutine 就能接收到。
Go 語言采用 CSP(Communicating Sequential Processes) 模型,也就是鼓勵:
💡 “不要通過共享內存來通信,而要通過通信來共享內存”
也就是通過 channel 來傳遞數據,而不是多個 goroutine 同時操作一份共享數據,這樣能減少復雜的鎖。
🧬 channel 的關鍵特性
? 類型化:
通道是有類型的,比如 chan int 只能傳 int,chan string 只能傳 string。
? 同步:
默認情況下,不緩沖通道是同步阻塞:
- 發送方(ch <- value)如果沒人接收,就等著。
- 接收方(<- ch)如果沒人發送,也等著。
? 緩沖區可選:
你也可以給通道設置緩沖區,比如 make(chan int, 10),這樣最多能存 10 個 int,不滿時發送方不會阻塞。
? 關閉通道:
當通道關閉(close(ch))后再接收,不會再阻塞,而是返回對應類型的零值和一個標志。
🎨 實際用法場景
- 任務分發:生產者 goroutine 往通道里傳任務,消費者 goroutine 從通道取任務。
- 信號通知:用通道只傳遞空 struct 來通知某個 goroutine 開始或結束工作。
- 并發控制:限制 goroutine 數量,比如用緩沖通道當作信號量。
go func()
go func() {channel <- "Hello, World!"
}()
這里啟動了一個新的 goroutine 來往通道中發送數據。
go func() { … }() 相當于讓任務異步執行,也就是你說的 Python 里的 create_task() 或者 await 背后的任務調度
當無緩沖通道必須一手給一手拿,所以你必須用 goroutine 來讓另一個任務并行接收,否則就死鎖。
假設你去掉 go func(),直接寫成:
channel := make(chan string)
channel <- "Hello, World!" // 直接發送
msg := <-channel
fmt.Println(msg)
那么這一行 channel <- “Hello, World!” 會一直卡住,因為:
- 這是無緩沖通道,必須同時有接收方(也就是 <- channel)在等著。
- 但是程序還沒執行到接收方那一行,所以就死鎖了。
用 go func() 把發送操作放到后臺 goroutine 中,這樣主 goroutine 可以繼續執行接收邏輯,不再相互卡住。
換句話說:
- goroutine A:負責發送
- goroutine B:負責接收
如果沒有 goroutine,把發送和接收寫在一條順序執行流里,就造成死鎖。
? 所有 channel 都必須這樣寫嗎?
不必須,取決于場景:
- 📌 如果你用緩沖通道,也就是:
ch := make(chan string, 1)
ch <- "Hello"
msg := <-ch
- 有緩沖,所以發送完能直接存進去,不會阻塞,這時候也可以不用 goroutine。
? 📌 如果你的接收方先執行,比如:
ch := make(chan string)
go func() {msg := <- chfmt.Println(msg)
}()
ch <- "Hello"
無緩沖通道要求
發送和接收必須成對并發執行
也可以用緩沖通道,這樣中間能暫存數據,就不用 goroutine。
無緩沖通道 vs 緩沖通道
類別 | 定義 | 行為 |
---|---|---|
無緩沖通道 | ch := make(chan string) | 必須一手交錢一手交貨(也就是發送時必須有接收方正在等著),否則發送方就堵住,不往下執行 |
有緩沖通道 | ch := make(chan string, N) | 通道內部有個緩沖區,當緩沖區沒滿時可以先把數據存起來,不需要接收方馬上接收,發送方能繼續往下走 |
🔍 為什么無緩沖通道單線程會卡?
無緩沖通道相當于是零容量隊列,你要往里面塞東西,但是根本沒有位置存,所以必須有人正在取,這時候才成對地完成操作。
你原來的例子:
channel := make(chan string) // 無緩沖通道
channel <- "Hello" // <=== 這里堵住了
msg := <-channel
為什么堵住?
因為這一行執行時:
- 當前 goroutine 在這里卡住,等待另一個 goroutine 來接收數據。
- 但是你下一行還沒執行,所以沒有接收者!于是永遠卡死。
- 🧑?🤝?🧑 無緩沖通道:必須當場給對方(必須有接收者正在等)。
- 📦 有緩沖通道:先放進快遞柜(緩沖區),接收方可以遲點取。
用 Python 類比一下也許更清楚:
- 無緩沖通道就像你用 await queue.put(),但是沒有任何任務在取 → 任務永遠卡在那里。
- goroutine 就像你用 create_task() 啟動另一個任務負責 queue.get(),那么 put() 就能順利執行完。
🔥 沒緩沖通道:必須有接收方同時存在,否則卡死,所以你要用 goroutine 來接收。
🔥 有緩沖通道:通道能先存東西,不必馬上接收,所以可以順利執行完發送,不用 goroutine。
普通 int 變量與 channel 的區別
對比點 | 普通 int 變量 | chan int 通道 |
---|---|---|
類型 | int 只存一個數字 | chan int 用來傳送數字給別人 |
用法 | num := 42 然后用 num | ch := make(chan int) 然后用 ch <- 42 傳數據 |
并發場景 | 只在當前 goroutine用 | 能在多個 goroutine之間傳遞數據,不共享狀態 |
行為 | 單純取值、賦值 | 發送必須有接收方,不然 goroutine 會等著 |
目的 | 存數據 | 讓 goroutine 之間通信 |
🎯 相當于什么現實場景?
💡 想象你有兩個工人:
- 工人A(goroutine A)負責生產東西(例如生產一個數字)。
- 工人B(goroutine B)負責接收東西然后打印出來。
那么:
- int num = 42 相當于是你自己手里拿著東西,沒給任何人。
- channel <- 42 相當于是你把東西放上傳送帶,另一個工人能從傳送帶拿東西,這樣兩個人之間就實現協作啦。
你可以用共享變量,但是:
- 要加鎖(防止讀寫沖突)。
- 要自己控制什么時候生產、什么時候消費,不然很可能出錯。
用 channel 你就不用自己實現這些復雜邏輯,Go 會幫你:
? 發送數據時自動等待接收方。
? 接收數據時自動等待生產方。
? 并發安全,不用你加鎖。