通道与通信
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 60 课,共 107 课。
通道是 Go 中 goroutine 之间通信的主要机制。与直接共享内存不同,goroutine 通过通道发送和接收值,这遵循 Go 的理念:“不要通过共享内存来通信,而要通过通信来共享内存。”
使用 make 函数和 chan 关键字创建一个通道:
messages := make(chan string)使用箭头运算符 <- 来发送和接收值。箭头指向数据流动的方向:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // 发送到通道
}()
msg := <-messages // 从通道接收
fmt.Println(msg) // 输出:hello
}默认情况下,发送和接收都会阻塞,直到另一端准备就绪。当 goroutine 发送 "hello" 时,它会等待 main 准备好接收。这种阻塞行为提供了内置的同步机制:不需要 time.Sleep。
你可以关闭通道,以表示不会再发送任何值。接收方可以检测到这一点:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}通道有类型限制:chan string 只能传递字符串。这种类型安全性可确保 goroutine 使用正确的数据类型进行通信,并在编译时而不是运行时捕获错误。
挑战
简单让我们构建一个消息中继系统,演示 goroutine 如何通过通道通信。你将创建一个流水线,让消息在不同的处理阶段之间流动,并让每个阶段并发运行。
你将把代码组织在两个文件中:
relay.go:定义消息处理流水线。创建一个
Message结构体,包含ID(int)和Content(string)字段。实现三个函数,分别代表流水线中的不同阶段:
Producer(messages []Message, out chan Message)- 接收一个消息切片,并将每条消息发送到输出通道。发送完所有消息后,关闭通道以表示处理完成。Transformer(in chan Message, out chan Message)- 从输入通道接收消息,将每条消息的内容转换为大写,然后把转换后的消息发送到输出通道。当输入通道关闭时(使用双值接收形式检测),关闭输出通道。Consumer(in chan Message) []string- 从输入通道接收所有消息,并将它们收集到一个格式化字符串切片中。每个字符串应采用以下格式:Message [ID]: [Content]。通道关闭后,返回该切片。
main.go:设置流水线,并协调并发消息流。读取消息数量,然后读取每条消息的 ID 和内容。创建消息,并设置两个通道来连接流水线的三个阶段。将 Producer 和 Transformer 作为 goroutine 启动,然后在主 goroutine 中运行 Consumer 来收集结果。将每条结果单独打印一行。
将提供以下输入:
- 第 1 行:消息数量(整数)
- 后续各行:每条消息占两行,先是消息 ID(整数),然后是消息内容(字符串)
例如,给定以下输入:
2
1
hello world
2
go channels你的输出应为:
Message 1: HELLO WORLD
Message 2: GO CHANNELS流水线的流程如下:Producer 发送消息 → Transformer 将内容转换为大写 → Consumer 收集结果并进行格式化。每个箭头都表示连接并发阶段的通道。使用 strings 包进行大写转换。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取消息数量
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// 读取消息
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: 创建两个通道,连接流水线的各个阶段
// TODO: 将 Producer 作为 goroutine 启动
// TODO: 将 Transformer 作为 goroutine 启动
// TODO: 在主 goroutine 中运行 Consumer 并收集结果
// TODO: 每行打印一个结果
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器