go 的 select 语句本身不会循环,它仅执行一次——当任一 case 就绪时即完成;若要持续监听多个通道,必须将其包裹在 for 循环中。
go 的 select 语句本身不会循环,它仅执行一次——当任一 case 就绪时即完成;若要持续监听多个通道,必须将其包裹在 for 循环中。
在 Go 中,select 是一种用于同时等待多个通道操作就绪的控制结构,但它不是循环语句。它的行为非常明确:一旦某个 case 中的通信操作(如 <-ch)成功完成(例如从通道接收到值),整个 select 块立即退出,后续代码继续执行——它不会自动重试或重复监听。这正是你程序只打印一次 "Eat" 后便终止的根本原因:select 执行完第一个接收后即结束,main 函数随之退出。
要让程序持续运行并反复响应各定时提醒,只需将 select 放入无限 for 循环中:
func main() {
ch1 := Remind("Eat", 3*time.Second) // 推荐写法:语义清晰,免去手动计算纳秒
ch2 := Remind("Work", 8*time.Second)
ch3 := Remind("Sleep", 24*time.Second)
for { // ? 关键:添加无限循环,使 select 持续工作
select {
case rem1 := <-ch1:
fmt.Println(rem1)
case rem2 := <-ch2:
fmt.Println(rem2)
case rem3 := <-ch3:
fmt.Println(rem3)
}
}
}此外,你的 Remind 函数存在一个潜在风险:它创建的是无缓冲通道(make(chan string)),而 goroutine 中每次发送前都未做任何同步检查。由于 select 在主 goroutine 中是非阻塞式轮询(每个循环只收一个值),且三个 goroutine 独立按固定周期发送,因此当前逻辑可正常工作。但更健壮、更符合 Go 习惯的实现应使用 time.Ticker,它专为周期性事件设计,资源更轻量、精度更可靠:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func Remind(text string, delay time.Duration) <-chan string {
ch := make(chan string, 1) // 建议加缓冲,避免 ticker 发送时主 goroutine 暂未 ready 导致阻塞
ticker := time.NewTicker(delay)
go func() {
defer ticker.Stop()
for range ticker.C {
msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
ch <- msg
}
}()
return ch
}⚠️ 注意事项:
立即学习“go语言免费学习笔记(深入)”;
- select 没有隐式循环,这是初学者常见误区;务必显式用 for {} 包裹;
- 若所有通道均阻塞且无 default 分支,select 会永久挂起(而非报错),适合做“守候式”监听;
- 使用 time.Second 等常量替代 1000*1000*1000,大幅提升可读性与可维护性;
- 对于纯定时任务,优先考虑 time.Ticker 而非手动 time.Sleep,因其可被安全停止且不易累积误差。
总结:理解 select 的一次性本质,是掌握 Go 并发控制流的关键一步。加上 for,它便成为协调多路事件的利器;脱离循环,它只是单次选择器——正确封装,方得始终。

















