Golang基礎筆記十之goroutine和channel
本文首發于公眾號:Hunter后端
這一篇介紹 Golang 里的 goroutine 和 channel 通道。
以下是本篇筆記目錄:
- goroutine
- channel
- goroutine 與 channel 的使用
1、goroutine
goroutine 是一種輕量級線程(用戶態線程),由 Go 運行時管理而非操作系統,它是 Go 并發模型的核心,能高效處理大量并發任務。
1. goroutine 的使用
goroutine 的使用非常簡單,直接使用 go + 函數 即可啟動一個 goroutine:
package main
import (
"fmt"
"time"
)
func PrintGoroutineInfo() {
fmt.Println("msg from goroutine")
}
func main() {
go PrintGoroutineInfo()
time.Sleep(1 * time.Millisecond)
fmt.Println("msg from main")
}
2. 匿名函數使用 goroutine
func main() {
go func() {
fmt.Println("msg from goroutine")
}()
time.Sleep(1 * time.Second)
}
而如果 goroutine 運行的函數有返回值,想要獲取函數的返回值應該如何操作呢?
或者當我們使用 goroutine 的時候,如何使主 goroutine 等待并發的 goroutine 執行完畢再接著往后執行呢?
其中一個方法就是使用 channel 來獲取返回值,以及使用 channel 的阻塞狀態來等待并發的 goroutine 執行完畢。
2、channel
channel,即通道,可用于在 goroutine 之間傳遞數據和同步狀態。
1. channel 的聲明與創建
channel 是強類型的,每個 channel 只能傳遞一種類型的數據。
1) 無緩沖通道
比如我們聲明一個傳遞 int 類型的 channel:
var ch chan int
或者直接創建一個傳遞 int 數據的通道:
ch := make(chan int)
2) 有緩沖通道
在創建 channel 的時候,如果不指定容量,那么則稱其為無緩沖通道,如果指定了容量,則為有緩沖通道,比如下面創建一個容量為 3 的通道:
ch := make(chan int, 3)
2. channel 的操作
發送數據
向一個 channel 發送數據的操作如下:
ch <- 21
接收數據
從一個 channel 中接收數據的操作如下:
x := <-ch
或者僅僅是接收數據但不使用,可以直接丟棄:
<-ch
使用 range 遍歷 channel
也可以使用 range 的方式遍歷從 channel 中接收數據,但是需要在通道關閉后:
for x := range ch {
fmt.Println(x)
}
關閉 channel
關閉一個 channel 的操作如下:
close(ch)
3、goroutine 與 channel 的使用
下面介紹幾種 channel 在使用中的特殊情況。
1. 阻塞情況
對于 channel 的使用,如果使用不慎,有可能會造成阻塞,以下是幾種阻塞的情況
1) 無緩沖通道
對于無緩沖通道而言,發送和接收的操作必須同時發生,否則會進入阻塞狀態。
func CapZeroChannel(ch chan int) {
time.Sleep(5 * time.Second)
ch <- 1
fmt.Println("inner func, send msg:", time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
ch := make(chan int)
go CapZeroChannel(ch)
fmt.Println("before func:", time.Now().Format("2006-01-02 15:04:05"))
x := <-ch
fmt.Println("after func:", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(x)
}
在上面的操作中,最后輸出的結果如下:
before func: 2025-06-28 23:33:03
inner func, send msg: 2025-06-28 23:33:08
after func: 2025-06-28 23:33:08
可以看到,在并發的 CapZeroChannel() 函數中,發送數據前等待了 5 秒鐘,同時在主 goroutine,也就是 main 函數中,通道接收值的地方發生了阻塞,直到發送方把數據通過 channel 發送過來,才接著往后執行。
而如果我們將等待的地方放在接收前:
func CapZeroChannel(ch chan int) {
ch <- 1
fmt.Println("inner func, send msg:", time.Now().Format("2006-01-02 15:04:05"))
}
func main() {
ch := make(chan int)
go CapZeroChannel(ch)
fmt.Println("before func:", time.Now().Format("2006-01-02 15:04:05"))
time.Sleep(5 * time.Second)
x := <-ch
fmt.Println("after func:", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(x)
}
最后輸出的信息如下:
before func: 2025-06-28 23:37:32
after func: 2025-06-28 23:37:37
inner func, send msg: 2025-06-28 23:37:37
可以看到發送的地方也同時發生了阻塞。
通過這兩個示例,可以證實前面的觀點,即 對于無緩沖通道而言,發送和接收的操作必須同時發生,否則會進入阻塞狀態。
2) 有緩沖通道
對于有緩沖通道來說,阻塞的情況會發生在向已滿的通道里發送數據,或者從空的通道里接收數據,下面是各自的示例:
向已滿的通道里發送數據
當我們向已滿的通道里發送數據,會發生阻塞,下面是代碼示例:
func SendMsgToChannel(ch chan int) {
ch <- 1
fmt.Println("send msg to channel: ", 1, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 2
fmt.Println("send msg to channel: ", 2, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 3
fmt.Println("send msg to channel: ", 3, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 4
fmt.Println("send msg to channel: ", 4, " at: ", time.Now().Format("2006-01-02 15:04:05"))
close(ch)
}
func main() {
ch := make(chan int, 3)
go SendMsgToChannel(ch)
time.Sleep(5 * time.Second)
for x := range ch {
fmt.Println("receive msg from channel: ", x, " at: ", time.Now().Format("2006-01-02 15:04:05"))
}
}
可以看到輸出的信息如下:
send msg to channel: 1 at: 2025-06-29 00:36:24
send msg to channel: 2 at: 2025-06-29 00:36:24
send msg to channel: 3 at: 2025-06-29 00:36:24
receive msg from channel: 1 at: 2025-06-29 00:36:29
receive msg from channel: 2 at: 2025-06-29 00:36:29
receive msg from channel: 3 at: 2025-06-29 00:36:29
receive msg from channel: 4 at: 2025-06-29 00:36:29
send msg to channel: 4 at: 2025-06-29 00:36:29
前面向通道里發送三條數據,把有緩沖通道占滿了,但是在接收前 sleep 了五秒鐘,所以沒有及時從通道里接收數據,當向通道里發送第四條數據的時候就會發生阻塞。
五秒鐘后,主 goroutine 開始從 channel 里消費數據,第四條數據才往里寫入。
從空通道里接收數據
如果從空通道里接收數據,也會發生阻塞,下面是代碼示例:
func SendMsgToChannel(ch chan int) {
time.Sleep(3 * time.Second)
ch <- 1
fmt.Println("send msg to channel: ", 1, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 2
fmt.Println("send msg to channel: ", 2, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 3
fmt.Println("send msg to channel: ", 3, " at: ", time.Now().Format("2006-01-02 15:04:05"))
ch <- 4
fmt.Println("send msg to channel: ", 4, " at: ", time.Now().Format("2006-01-02 15:04:05"))
close(ch)
}
func main() {
ch := make(chan int, 3)
go SendMsgToChannel(ch)
fmt.Println("start receive msg from channel at: ", time.Now().Format("2006-01-02 15:04:05"))
for x := range ch {
fmt.Println("receive msg from channel: ", x, " at: ", time.Now().Format("2006-01-02 15:04:05"))
}
}
其輸出信息如下:
start receive msg from channel at: 2025-06-29 00:41:24
receive msg from channel: 1 at: 2025-06-29 00:41:27
send msg to channel: 1 at: 2025-06-29 00:41:27
send msg to channel: 2 at: 2025-06-29 00:41:27
send msg to channel: 3 at: 2025-06-29 00:41:27
send msg to channel: 4 at: 2025-06-29 00:41:27
receive msg from channel: 2 at: 2025-06-29 00:41:27
receive msg from channel: 3 at: 2025-06-29 00:41:27
receive msg from channel: 4 at: 2025-06-29 00:41:27
可以看到,在發送信息前 sleep 了 3 秒,因此接收方也等待了 3 秒才能開始接收數據,在這之前一直是處于阻塞狀態。
2. 通道的關閉
通道在關閉后,不可以再向其中發送數據,但是還可以從中接收數據。
func PrintGoroutineInfo(ch chan int) {
fmt.Println("msg from goroutine")
time.Sleep(1 * time.Second)
ch <- 2
close(ch)
}
func main() {
ch := make(chan int)
go PrintGoroutineInfo(ch)
x := <-ch
fmt.Println(x)
}
3. 發送和接收通道的類型
前面我們將通道作為參數傳遞給函數,其類型僅僅是通道類型,如果我們想要在代碼層面使其更嚴謹,比如某個函數中只允許發送或者接收數據,我們在其類型進行更嚴謹的聲明。
比如發送通道我們可以對其聲明為 chan<-,接收通道可以對其聲明為 <-chan,下面是代碼示例:
func SendMsg(ch chan<- int) {
ch <- 4
fmt.Println("send msg: ")
}
func ReceiveMsg(ch <-chan int) {
x := <-ch
fmt.Println("receive msg: ", x)
}
func main() {
ch := make(chan int, 3)
go SendMsg(ch)
go ReceiveMsg(ch)
time.Sleep(1 * time.Second)
fmt.Println("end")
}
在這篇筆記中,我們介紹 goroutine 和 channel 在 Golang 中的使用,比如如何使用 goroutine 開啟一個并發操作,如何使用 channel 在 goroutine 間進行通信。
但是關于 goroutine 之間的一些并發控制與鎖相關的一些概念我們將在之后的筆記中進行更詳細的介紹。

浙公網安備 33010602011771號