Go的秘密生活:select 语句

发布: (2026年2月22日 GMT+8 12:59)
5 分钟阅读
原文: Dev.to

Source: Dev.to

如何阻止快速数据等待慢速通道

第 25 部分:多路复用器、超时和非阻塞读取

Ethan 正在观看终端输出一行行滴下来。速度慢得令人痛苦。

“我不明白,”他揉着眼睛说。“我有两个 goroutine 在发送数据。一个是本地缓存,返回时间为一毫秒。另一个是网络调用,需要五秒。但快速数据却在等待慢速数据。”

Eleanor 走过去查看他的代码。

问题代码

func process(cacheChanYou have created a traffic jam,” Eleanor observed. “Channel receives are blocking. Because you asked for `netChan` first, your function halts right there. It doesnt matter that `cacheChan` has been ready for 4.99 seconds. You are forcing a sequential read on concurrent data.”

我该如何读取先准备好的那个? Ethan 问。

你需要一个多路复用器。 在 Go 中,我们使用 select 语句。

select 语句

Eleanor 重写了他的函数。select 语句看起来和 switch 完全一样,但它只针对通道操作。

解决方案

func process(cacheChanExactly,” Eleanor said. “`select` listens to all its cases simultaneously. Whichever channel is ready first, it executes that case. If multiple channels are ready at the same time, Go picks one completely at random to ensure fairness.”

超时(time.After

Ethan 想知道如果网络宕机,netChan 永远不发送任何东西会怎样。

“目前,”Eleanor 回答,“你的 select 会永远等待。这会导致 goroutine 泄漏。在生产代码中必须强制超时。”

添加超时

func processWithTimeout(cacheChan`time.After` acts as a ticking time bomb,” Eleanor explained. “If `netChan` or `cacheChan` dont respond within two seconds, the timeout case wins the race.”

安全提示:time.After 会创建一个计时器,直到触发前一直存在。如果你把它放在一个快速、紧凑的循环中,会导致内存泄漏。这种情况下请使用 time.NewTimer 并显式 Stop()。对于更复杂的多层超时,建议使用 context.WithTimeout(如第 22 集所示)。

非阻塞读取(default

“如果我根本不想等待怎么办?我只想窥探一下通道,如果有数据就取走,没有的话立刻去做别的事,”Ethan 询问。

“这时就使用 default 分支,”Eleanor 微笑着说。

示例

func checkStatus(statusChanA `select` with a `default` case is completely nonblocking,” she explained. “If no channels are ready that exact microsecond, it falls through to the `default` block immediately.”

Ethan 靠在椅背上说:“所以我可以一次等待多个事件,设定时间限制,甚至完全不等待。”

“正是如此,”Eleanor 说。“你不再受 goroutine 的摆布,而是对它们进行编排。”

关键概念

select 语句

  • 一种控制结构,允许 goroutine 在多个通信操作上等待。
  • 它会阻塞,直到其中一个 case 可以执行。
  • 如果有多个 case 已就绪,则随机选择一个执行。

select

  • select {}(没有任何 case)会永久阻塞当前 goroutine。

超时与内存

  • time.After(duration) 适用于简单、一次性的超时。
  • 生产环境警告: 在紧密循环中,使用 time.NewTimer(duration) 并调用 .Stop() 以避免内存泄漏。
  • 对于复杂的、多层次的超时,推荐使用 context.WithTimeout

循环标签

  • 若要在 select 内部跳出 for 循环,必须使用标签(例如 break outer)。普通的 break 只能退出 select

非阻塞操作(default

  • 添加 default case 会使 select 变为非阻塞。如果没有其他通道就绪,它会立即执行。

下一集:工作池 – Ethan 学习如何并发处理成千上万的任务。

  • 使用固定数量的 goroutine 来防止内存耗尽。
  • Aaron Rose 是 tech-reader.blog 的软件工程师和技术作者,也是《Think Like a Genius》的作者,详情请见 Think Like a Genius
0 浏览
Back to Blog

相关文章

阅读更多 »

Golang 并发模式:Fan-out / Fan-in

此模式可以解决的问题——你有 8 核心,但数据处理阶段在单个 goroutine 中顺序运行。CPU 在任务队列时处于空闲状态……