周期执行time.Ticker
time.Ticker 的功能
time.Ticker 通过一个通道(Ticker.C)每隔固定时间间隔发送时间信号。
它允许用户在指定的时间间隔内执行某些任务,而无需手动管理定时器的触发。
go
type Ticker struct {
C <-chan Time // 定时器通道
Stop chan struct{} // 停止信号通道
period Duration // 间隔时间
}
C: Ticker 的核心部分是通道 C,它每当时间到达指定间隔时发送一个时间点(time.Time)。
Stop: Stop 是一个用于停止定时器的通道,调用 Stop() 方法时会关闭这个通道,从而终止定时器的事件发送。
period: period 是定时器的时间间隔,表示触发事件的频率。
- Ticker 的工作原理
Ticker 的核心是通过一个后台的协程不断触发事件。其工作方式大致如下:
定时器启动: 当我们调用 time.NewTicker(d) 创建一个定时器时,Go 会创建一个新的协程,它会每隔 d 的时间间隔将当前时间发送到 Ticker.C 通道中。
事件触发: 每次时间间隔到达时,后台的协程会向 Ticker.C 通道发送一个当前的时间 time.Time 对象,允许主程序或其他协程从通道中读取事件。
停止定时器: 当调用 Ticker.Stop() 时,后台的协程会停止发送事件,同时关闭 Ticker.C 通道。
这种基于协程和通道的设计模式保证了定时任务的高效和线程安全。
使用 time.Ticker 实现周期性任务
go
func main() {
ticker := time.NewTicker(2 * time.Second) // 每2秒触发一次
defer ticker.Stop() // 程序结束时停止 ticker
for {
select {
case t := <-ticker.C: // 从 ticker 的 channel 接收时间
fmt.Println("Current time:", t)
}
}
}
time.NewTicker(2 * time.Second) 创建了一个新的定时器,它会每隔 2 秒触发一次。
ticker.C 是一个 channel,select 语句可以从中读取到事件。
在 select 语句中读取 ticker.C 时,程序会等待并处理每次触发的事件。
Current time: 2025-10-04 02:23:36.3368219 +0800 CST m=+2.000000001
Current time: 2025-10-04 02:23:38.3368219 +0800 CST m=+4.000000001
Current time: 2025-10-04 02:23:40.3368219 +0800 CST m=+6.000000001
Current time: 2025-10-04 02:23:42.3368219 +0800 CST m=+8.000000001
Current time: 2025-10-04 02:23:44.3368219 +0800 CST m=+10.000000001
Current time: 2025-10-04 02:23:46.3368219 +0800 CST m=+12.000000001
Current time: 2025-10-04 02:23:48.3368219 +0800 CST m=+14.000000001
Current time: 2025-10-04 02:23:50.3368219 +0800 CST m=+16.000000001
处理超时或取消周期性任务
如果需要在特定条件下停止周期性任务,可以使用 context 包结合 time.Ticker 或协程来实现超时或取消操作。
在 10 秒后停止周期性任务
go
package main
import (
"context"
"fmt"
"time"
)
func periodicTask(ctx context.Context) {
ticker := time.NewTicker(2 * time.Second) // 每2秒触发一次
defer ticker.Stop()
for {
select {
case <-ctx.Done(): // 当上下文被取消时退出循环
fmt.Println("Periodic task stopped.")
return
case t := <-ticker.C:
fmt.Println("Current time:", t)
}
}
}
func main() {
// 创建一个带有超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() // 确保超时后取消上下文
go periodicTask(ctx) // 在独立的协程中执行周期性任务
// 等待上下文超时
time.Sleep(12 * time.Second) // 程序运行一段时间后退出
}
context.WithTimeout() 创建了一个带有超时的上下文,ctx.Done() 在超时后会被关闭。
periodicTask 函数接收上下文 ctx,并在每次周期性执行时检查 ctx.Done() 是否关闭。
一旦超时,ctx.Done() 会被触发,periodicTask 中的循环退出,任务停止。
结合 time.Ticker 和 context 控制周期性任务的停止
在实际应用中,可能需要在某些条件下停止周期性任务,这时可以通过 context 来传递取消信号。
go
package main
import (
"context"
"fmt"
"time"
)
func periodicTask(ctx context.Context) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
fmt.Println("Periodic task stopped due to context cancellation.")
return
case t := <-ticker.C:
fmt.Println("Current time:", t)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go periodicTask(ctx) // 启动周期性任务
// 模拟一段时间后停止任务
time.Sleep(6 * time.Second)
cancel() // 取消上下文,停止周期性任务
// 等待任务结束
time.Sleep(2 * time.Second)
}
context.WithCancel 创建了一个可以手动取消的上下文。
调用 cancel() 可以取消上下文,触发 ctx.Done(),停止周期性任务。