ZetCode

Go Channel

最后修改时间:2025 年 5 月 3 日

在本文中,我们将展示如何在 Golang 中使用 channels。

一个 goroutine 是 Go 中的一个轻量级执行线程,能够实现函数的并发执行。与传统线程不同,goroutine 非常高效且开销极小,这使得它们非常适合处理并发任务,而不会造成过度的资源消耗。

Goroutines 独立运行,但共享相同的地址空间,这允许它们之间进行高效的通信。由于它们与其他正在运行的代码并行执行,因此它们使开发人员能够同时执行多个操作,例如处理请求、处理 I/O 操作或管理后台计算。

为了实现 goroutines 之间安全同步的通信,Go 提供了 channels。Channels 作为数据交换的管道,允许 goroutines 以结构化的方式发送和接收信息。通过使用 channels,开发人员可以协调执行、防止竞态条件,并确保并发函数之间平滑的数据传输。

通过 goroutines 和 channels 的组合,Go 实现了高效的并发管理,使其成为处理可伸缩、高性能应用程序的强大语言。

Channel 函数和运算符

在本节中,我们将介绍与 channels 相关的函数和运算符。

c := make(chan int)

Channel 使用 make 函数和 chan 关键字创建。Channel 会被赋予一个类型。

<- 运算符用于读取 channel 和写入 channel。

c <- v

这行代码将一个值写入 channel。

<- c

这行代码从 channel 读取一个值。

close(c)

Channel 使用 close 函数关闭。

for e := range(c) {
    fmt.Println(e)
}

我们可以使用 range 遍历从 channel 发送的值。

len(c)

len 函数返回已成功发送到 channel 但尚未取出的元素的数量。

注意: 默认情况下,发送者和接收者会阻塞,直到 channel 的另一端准备就绪。这允许 goroutines 在没有显式锁或条件变量的情况下进行同步。

单向 Channels

默认情况下,channels 是双向的。它们都可以用于发送和接收数据。

rc chan<- int

将箭头运算符放在 chan 关键字的右侧,我们创建了一个只能接收的 channel。

wc <-chan int

将箭头运算符放在 chan 关键字的左侧,我们创建了一个只能发送的 channel。

Go Channel 简单示例

以下是一个使用 channel 的简单示例。

main.go
package main

import "fmt"

func main() {

    c := make(chan string)

    go func() {

        c <- "an old falcon"
    }()

    msg := <-c

    fmt.Println(msg)
}

在程序中,我们有两个 goroutines。一个使用 go 关键字创建;主函数也是一个 goroutine。

c := make(chan string)

我们创建一个 string 类型的 channel。

go func() {

    c <- "an old falcon"
}()

在匿名的 goroutine 中,我们将一个 string 消息发送到 channel。

msg := <-c

在主 goroutine 中,我们从 channel 读取值。

$ go run main.go
an old falcon

Go Channel 死锁

当代码在使用 channel 时被阻塞时,就会发生 channel 死锁。当我们尝试从一个打开的空 channel 读取,或者尝试向一个打开的满 channel 写入时,channel 就会被阻塞。

main.go
package main

import "fmt"

func main() {

    c := make(chan string)

    c <- "an old falcon"
    msg := <-c

    fmt.Println(msg)
}

此程序以死锁结束。

c <- "an old falcon"

我们将一个值写入 channel。channel 会阻塞并等待直到它被清空。因此,读取 channel 并清空它的那一行代码将无法到达。这种情况会导致死锁。

$ go run main.go
fatal error: all goroutines are asleep - deadlock!
...

Go Channel Range

我们可以使用 range 关键字遍历发送到 channel 的数据。

main.go
package main

import (
    "fmt"
    "time"
)

func main() {

    c := make(chan string)
    go hello("Martin", c)

    for msg := range c {

        fmt.Println(msg)
    }
}

func hello(name string, c chan string) {

    for i := 0; i < 5; i++ {

        msg := fmt.Sprintf("Hello %s!", name)
        c <- msg
        time.Sleep(time.Millisecond * 500)
    }

    close(c)
}

在程序中,我们将五个消息发送到一个 channel。

go hello("Martin", c)

使用 go 创建一个 hello goroutine。我们将 channel 作为参数传递。

for msg := range c {

    fmt.Println(msg)
}

使用 range 关键字,我们遍历消息并将其打印到控制台。

func hello(name string, c chan string) {

    for i := 0; i < 5; i++ {

        msg := fmt.Sprintf("Hello %s!", name)
        c <- msg
        time.Sleep(time.Millisecond * 500)
    }

    close(c)
}

hello 函数中,我们创建了五个消息并通过 channel 发送给 main goroutine。当 goroutine 完成后,我们使用 close 关闭 channel。

$ go run main.go
Hello Martin!
Hello Martin!
Hello Martin!
Hello Martin!
Hello Martin!

单向 Channel 示例

在以下示例中,我们使用单向 channels。

main.go
package main

import "fmt"

func main() {

    c1 := make(chan int)
    c2 := make(chan int)

    go power(c2, c1)
    go power(c2, c1)
    go power(c2, c1)

    c2 <- 2
    fmt.Println(<-c1)

    c2 <- 4
    fmt.Println(<-c1)

    c2 <- 5
    fmt.Println(<-c1)

}

func power(wc <-chan int, rc chan<- int) {

    num := <-wc
    res := num * num
    rc <- res
}

在程序中,我们有一个 power 函数,它接受一个只写 wc channel 和一个只读 rc channel。

$ go run main.go
4
16
25

来源

The Go Programming Language Specification

在本文中,我们学习了 Go channels。

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 Go 教程