Go 并发 第2部分:Channel
Source: Dev.to
Channel
“不要通过共享内存来通信;相反,要通过通信来共享内存。”
在 Go 中,goroutine 通常通过 channel 交换数据,而不是修改共享变量。
- 发送一个值:
c <- v - 接收一个值:
v := <-c
c 是一个带类型的 channel;其元素类型在创建 channel 时就已确定。
func multiply(v1, v2 int, c chan int) {
c <- v1 * v2
}
func main() {
c := make(chan int)
go multiply(1, 2, c)
go multiply(3, 4, c)
a := <-c
b := <-c
fmt.Println(a * b) // prints 24
}
channel 的内置阻塞语义使得显式的 WaitGroup 成为不必要:发送会阻塞直到有接收者准备好,接收会阻塞直到有值可用。
使用 channel 进行简单同步
channel 可以充当一次性信号,类似于 WaitGroup。
ready := make(chan bool)
go func() {
// do some work...
ready <- true // signal completion
}()
<-ready // wait until the goroutine signals
接收到的值通常会被忽略;程序仅仅是等待发送发生。
使用 for range 接收流
当生产者关闭 channel 时,接收者可以遍历直到 channel 被关闭。
func producer(c chan int) {
for i := 1; i <= 5; i++ {
fmt.Println("Sending", i)
c <- i
time.Sleep(500 * time.Millisecond)
}
close(c)
}
func main() {
c := make(chan int)
go producer(c)
for v := range c {
fmt.Print("Receiving ", v)
}
fmt.Println("\nDone")
}
重要提示: 发送方在不再发送值时应关闭 channel。从接收方关闭 channel 会在发送方仍然活跃时导致 panic。
缓冲通道 vs 非缓冲通道
默认情况下,channel 的容量为 0(非缓冲),每次发送都会与相应的接收同步。
缓冲通道允许在不阻塞发送方的情况下排队有限数量的值:
c := make(chan int, 3) // capacity 3
- 前三个发送会立即成功。
- 第四个发送会阻塞,直到有接收者读取一个值。
- 当缓冲区为空时,接收会阻塞。
当生产者和消费者的运行速度不同、需要平滑临时不匹配时,缓冲通道非常有用。
方向性通道
当 channel 作为函数参数传递时,可以限制其方向:
- 仅发送:
func producer(c chan<- int) - 仅接收:
func consumer(c <-chan int)
这使得意图更加明确,并帮助编译器捕获误用。
select 语句
select 像是针对 channel 操作的 switch。它会阻塞,直到其中的某个 case 能够继续执行。
基本用法
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "A"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "B"
}()
select {
case res := <-c1:
fmt.Println("Received from c1:", res)
case res := <-c2:
fmt.Println("Received from c2:", res)
}
}
程序会打印首先准备好的通道的值(本例中是 c1)。
在发送和接收之间选择
select {
case a := <-inChan:
fmt.Println("Received:", a)
case outChan <- b:
fmt.Println("Sent:", b)
}
常见模式
超时
select {
case res := <-c1:
fmt.Println("Received:", res)
case <-time.After(5 * time.Second):
fmt.Println("Timeout")
}
非阻塞 default
select {
case res := <-c1:
fmt.Println("Received:", res)
default:
fmt.Println("No message")
}
中止信号
select {
case res := <-c1:
fmt.Println("Received:", res)
case <-abortChan:
fmt.Println("Abort")
return
}