Menu
CoddyTech

通道与通信

本课属于 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 使用正确的数据类型进行通信,并在编译时而不是运行时捕获错误。

challenge icon

挑战

简单

让我们构建一个消息中继系统,演示 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: 每行打印一个结果
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器