前言
在 Go 语言中,goroutine 是轻量级的并发执行单元,它使得我们能够非常方便地实现并发编程。本文将通过一个经典的案例 —— 使用两个 goroutine 交替打印数字和字母,来演示如何利用 channel 控制 goroutine 的执行顺序,并加深对 Go 并发模型的理解。
🎯 题目描述
编写一个 Go 程序,使用两个 goroutine:
一个 goroutine 打印数字 1~26;
另一个 goroutine 打印字母 ‘A’~‘Z’;
要求输出结果为:
深色版本
1 2 A 3 4 B 5 6 C … 25 26 Z
即每打印两个数字后打印一个字母,形成交错输出的效果。
🧠 解题思路
要实现两个 goroutine 按照一定顺序交替执行,关键在于控制执行流程。我们可以借助 Go 的 channel 实现同步通信机制:
使用两个 channel:numChan 和 letterChan,分别用于通知打印数字和字母;
主 goroutine 控制初始信号;
每个 goroutine 打印完相应内容后发送下一个 goroutine 的信号;
使用计数器控制循环结束。
✅ 完整代码实现
package main
import (
"fmt"
"time"
)
func main() {
numChan := make(chan struct{})
letterChan := make(chan struct{})
done := make(chan struct{})
// 数字 goroutine
go func() {
for i := 1; i <= 26; i += 2 {
// 等待打印两个数字的信号
<-numChan
// 打印两个数字
fmt.Printf("%d %d ", i, i+1)
// 发送打印字母的信号
letterChan <- struct{}{}
}
close(letterChan)
done <- struct{}{}
}()
// 字母 goroutine
go func() {
for ch := 'A'; ch <= 'Z'; ch++ {
// 等待打印字母的信号
<-letterChan
// 打印一个字母
fmt.Printf("%c ", ch)
// 如果还没到结尾,通知继续打印下一组数字
if ch != 'Z' {
numChan <- struct{}{}
} else {
done <- struct{}{}
}
}
}()
// 启动第一个信号
numChan <- struct{}{}
// 等待所有任务完成
<-done
<-done
fmt.Println("\n程序结束")
}
🔍 运行结果示例
1 2 A 3 4 B 5 6 C 7 8 D 9 10 E 11 12 F 13 14 G 15 16 H 17 18 I 19 20 J 21 22 K 23 24 L 25 26 Z
程序结束
🛠️ 代码解析
- Channel 的作用
numChan: 控制是否可以打印数字(初始由主 goroutine 触发);
letterChan: 控制是否可以打印字母;
done: 用于主 goroutine 等待子 goroutine 结束。 - 执行流程
主 goroutine 发送第一个信号给 numChan,启动数字打印;
数字 goroutine 打印两个数字后,发送信号给 letterChan;
字母 goroutine 收到信号后打印一个字母,并再发送信号给 numChan;
循环往复,直到全部数据打印完毕;
最终主 goroutine 接收到 done 信号,程序退出。
🧪 小技巧
在实际开发中,可以通过 time.Sleep() 模拟延迟,观察 goroutine 执行顺序;
如果希望更清晰地看到输出,可以在打印语句中加入换行或空格;
使用 sync.WaitGroup 替代 done channel 也是常见做法。
📚 扩展练习建议
使用 WaitGroup 替代 done channel;
修改题目要求为:打印一次数字后打印两次字母;
尝试使用 context.Context 控制 goroutine 的取消操作;
封装成函数,支持任意数量的交替规则;
📝 总结
通过这个小例子,我们学习了以下 Go 并发编程的核心知识点:
技术点 | 应用场景说明 |
---|---|
Goroutine | 并发执行多个任务 |
Channel | 协调 goroutine 之间的执行顺序 |
主 goroutine 等待 | 确保程序不会提前退出 |