Go语言的通道(Channel)是实现goroutine之间通信和同步的重要机制,其阻塞特性直接影响并发程序的行为。当通道操作无法满足继续条件时,当前goroutine会被挂起,直到条件成立。理解这些阻塞规则是掌握Go并发编程的基础。

通道阻塞的基本规则
根据通道是否有缓冲,阻塞行为有所区别。无缓冲通道要求发送和接收同时就绪,否则双方都会阻塞。有缓冲通道在缓冲区满时发送阻塞,在缓冲区空时接收阻塞。
无缓冲通道示例
下面的代码演示了无缓冲通道的发送阻塞,必须另有goroutine接收才能继续:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int) // 无缓冲通道
go func() {
time.Sleep(time.Second)
ch <- 100 // 1秒后发送,避免主协程永久阻塞
}()
val := <-ch // 主协程接收,若没有发送者则会阻塞在此
fmt.Println("received:", val)
}
有缓冲通道示例
有缓冲通道在缓冲满时发送阻塞,在缓冲空时接收阻塞:
package main
import "fmt"
func main() {
ch := make(chan int, 2) // 容量为2的缓冲通道
ch <- 1
ch <- 2
// ch <- 3 // 若取消注释,此处会因缓冲满而阻塞
fmt.Println(<-ch)
fmt.Println(<-ch)
// fmt.Println(<-ch) // 若取消注释,此处会因缓冲空而阻塞
}
常见并发模式
生产者消费者模式
使用通道作为队列,多个生产者发送数据,多个消费者处理数据,通过关闭通道通知结束:
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int, 5)
var wg sync.WaitGroup
// 生产者
for p := 0; p < 2; p++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for i := 0; i < 3; i++ {
ch <- id*10 + i
}
}(p)
}
// 消费者
for c := 0; c < 2; c++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for v := range ch {
fmt.Printf("consumer %d got %dn", id, v)
}
}(c)
}
wg.Wait()
close(ch)
}
任务分发与信号通知
利用close(chan)的广播特性,可以实现一对多的退出通知。所有接收者会在通道关闭后立即收到零值并退出循环:
package main
import (
"fmt"
"time"
)
func worker(stop chan struct{}, id int) {
for {
select {
case <-stop:
fmt.Printf("worker %d stoppedn", id)
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}
func main() {
stop := make(chan struct{})
for i := 0; i < 3; i++ {
go worker(stop, i)
}
time.Sleep(time.Second)
close(stop) // 广播停止信号
time.Sleep(200 * time.Millisecond)
}
避免死锁的建议
死锁常因所有goroutine均阻塞而产生。编写时应注意:
- 确保发送和接收方都有机会执行,不要在主协程单独向无缓冲通道发送而无接收者。
- 使用
select配合default或超时分支,避免永久阻塞。 - 多生产者场景下,由唯一地方关闭通道,防止重复关闭引发panic。
掌握通道阻塞特性与典型并发模式,能够让我们更安全高效地利用Go的并发能力,构建稳定的后端服务。
Go_channelgoroutineconcurrency_pattern修改时间:2026-07-26 06:18:20