应使用 time.Ticker 实现稳定间隔提醒,因其由 runtime 定时器驱动、精度高、不累积延迟;需显式调用 Stop() 防泄漏;若处理耗时超间隔,tick 会被丢弃,需按需选用 AfterFunc 或带缓冲 channel 方案。
time.Ticker 实现稳定间隔提醒直接用 time.Sleep 轮询做提醒,时间漂移严重,尤其在系统负载高或 GC 触发时。正确做法是用 time.Ticker,它由 runtime 定时器驱动,精度更高、调度更可靠。
注意:time.Ticker 不会自动停止,必须显式调用 ticker.Stop(),否则 goroutine 和 timer 会泄漏。
time.Duration 是「理想间隔」,实际触发时间可能略晚(但不会累积)Tick 通道会阻塞,后续 tick 会被丢弃 —— 这是设计行为,不是 bugtime.AfterFunc 链式重启,或用带缓冲的 channel + 单独 worker goroutineticker := time.NewTicker(5 * time.Second) defer ticker.Stop()for { select { case <-ticker.C: fmt.Println("提醒:检查邮件") } }
for { time.Sleep() } 里直接写提醒逻辑看似简洁,实则隐藏三类问题:GC STW 导致休眠被拉长、系统时间调整(如 NTP 同步)让 Sleep 提前/延后返回、无法响应退出信号。
更关键的是:这种写法把「调度权」完全交给用户代码,绕过了 Go runtime 的 timer 优化路径(比如合并相近定时器),在高并发提醒场景下资源开销明显上升。
time.Sleep 是阻塞当前 goroutine,但不释放 P,可能影响其他 goroutine 调度公平性SIGINT,需额外加 channel 监听,逻辑变臃肿Ticker 仍按原节奏恢复,而 Sleep 会直接跳过中间所有提醒提醒程序常伴随长期运行的 goroutine,最容易因 channel 关闭不一致或忘记 stop 而泄漏。常见模式有两类:
context.Context.Done())time.After 或 time.Tick 创建临时 timer,但未在退出前 stop —— 尤其在频繁启停提醒功能时验证是否泄漏:运行时加 GODEBUG=gctrace=1,观察 goroutine 数是否随提醒次

pprof/goroutine 快照比对。
用 time.Now().Hour() == 9 判断「早上 9 点提醒」是危险的 —— 它依赖系统本地时区,且夏令时切换当天可能出现两次或零次匹配。
正确方式是用 time.Location 显式指定时区,并借助 time.Date 构造目标时刻再比较:
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
target := time.Date(now.Year(), now.Month(), now.Day(), 9, 0, 0, 0, loc)
if now.After(target) && now.Before(target.Add(1 * time.Minute)) {
fmt.Println("该提醒了")
}更健壮的做法是结合 time.Ticker 每分钟检查一次,避免因系统时间跳变导致漏判。别依赖 time.ParseInLocation 解析固定字符串时间 —— 夏令时边界解析结果可能出人意料。