Go 并发 第2部分:Channel

发布: (2026年5月1日 GMT+8 09:31)
4 分钟阅读
原文: Dev.to

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
}
0 浏览
Back to Blog

相关文章

阅读更多 »

Go 零值:var 与 :=

Go 有两种声明变量的方式:var 和 :=。它们存在的原因不同,规则也不同。了解何时使用每一种可以避免低级错误和 c…

模型越智能,节省越多。

神话:更智能的模型会让插件变得多余。自从 WOZCODE 推出以来,许多 Claude Code 高级用户低声说插件的优势将会消失。