首页 > 编程笔记 > Go语言笔记 阅读:2

Go语言定时器的用法(附带实例)

定时器使我们能够延迟执行任务或定期执行任务。Go 语言的 time 包提供了与时间和定时器相关的 API,例如获取当前系统时间(精确到纳秒级)、让协程休眠指定时间、延迟指定时间执行任务等。

下面我们通过几个 Go 程序进行演示:
1) 第一个程序演示了如何让协程休眠指定时间,代码如下所示:
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("main start:", time.Now().Format("2006-01-02 15:04:05"))
    // 协程休眠 3s
    time.Sleep(3 * time.Second)
    fmt.Println("main end:", time.Now().Format("2006-01-02 15:04:05"))
}
输出结果为:

main start: 2025-08-21 09:51:50
main end: 2025-08-21 09:51:53

函数 time.Sleep 会暂停当前协程的运行,输入参数表示暂停时间,单位为 ns。根据上面的程序,我们让主协程休眠 3s,所以第二次输出语句与第一次输出语句相差 3s。

注意,实际上主协程至少会暂停 3s,也有可能暂停更长时间,因为如果 Go 程序任务比较繁忙,那么 3s 之后即使主协程可以运行,也可能无法被调度器调度执行。

方法 Format 用于格式化输出时间,注意上面使用的格式是 2006-01-02 15:04:05,这有什么特殊含义吗?使用其他格式如 2023-03-25 20:27:21可以吗?按理说这两个时间字符串的格式一样,只是值不一样罢了,结果应该没区别。我们可以编写一个简单的 Go 程序进行测试,代码如下所示:
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Format("2023-03-25 20:27:21"))
}
输出结果为:

21219-09-214 210:217:218

参考上面的程序,输出结果是一个很奇怪的字符串,并不是我们想要的年月日时分秒格式。这是为什么呢?

这就需要研究下 Go 语言的 Format 方法支持的格式标识,比如其他一些语言使用 Y 表示年,M 表示月等。Go 语言定义的年月日等标识如下所示:
const (
    stdZeroMonth = iota + stdNeedDate // "01"
    stdZeroDay                        // "02"
    stdHour                           // "15"
    stdZeroMinute                     // "04"
    stdZeroSecond                     // "05"
    stdLongYear                       // "2006"
    // 省略了其他标识定义
)
看到了吧,2006、01 等才是 Go 语言定义的时间格式标识,所以 2006-01-02 15:04:05才能正常显示年月日时分秒。当然,这里还省略了很多其他时间格式标识,可以参考 time/format.go 文件。

2) 第二个程序演示如何延迟指定时间执行某项任务,代码如下所示:
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("main start:", time.Now().Format("2006-01-02 15:04:05"))
    // 1 s 后执行函数
    time.AfterFunc(time.Second, func() {
        fmt.Println("exec task:", time.Now().Format("2006-01-02 15:04:05"))
    })
    time.Sleep(3 * time.Second)
}
输出结果为:

main start: 2025-08-21 09:54:26
exec task: 2025-08-21 09:54:27

参考上面的程序,函数 time.AfterFunc 用于延时执行一个函数,其中有两个参数:
可以看到,结果符合我们的预期。

3) 第三个程序演示如何周期性执行某项任务,代码如下所示:
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("main start:", time.Now().Format("2006-01-02 15:04:05"))
    // 以 1 s 为周期,定时触发
    ticker := time.NewTicker(time.Second)
    go func() {
        for {
            <-ticker.C // 时间触发时,管道可读
            fmt.Println("exec task:", time.Now().Format("2006-01-02 15:04:05"))
        }
    }()
    time.Sleep(3 * time.Second)
}
输出结果为:

main start: 2025-08-21 09:55:30
exec task: 2025-08-21 09:55:31
exec task: 2025-08-21 09:55:32

参考上面的程序,函数 time.NewTicker 用于创建一个周期性的定时器,注意子协程的主体是一个循环,首先从管道 ticker.C读取数据,读取到数据之后会输出一条语句。这是什么原理呢?其实是因为通过函数 time.NewTicker 创建的定时器,会周期性地向管道 ticker.C 写数据,所以子协程才能周期性地从管道 ticker.C 读取到数据,以此保证任务的周期性执行。

相关文章