在go语言中,定时器是并发编程中常用的工具之一。定时器可以用于监控某个goroutine的运行时间、定时打印日志、周期性执行任务等多种场景。
go标准库提供了两种主要的定时器:timer(一次性定时器)和ticker(周期性定时器)。本文将详细介绍这两种定时器的用法,并通过实际案例展示其应用场景。
一、timer定时器
timer定时器是一种一次性定时器,即在未来某个时刻触发的事件只会执行一次。
timer的结构中包含一个time类型的管道c,主要用于事件通知。
在未到达设定时间时,管道内没有数据写入,一直处于阻塞状态;到达设定时间后,会向管道内写入一个系统时间,触发事件。
1. 创建timer
使用time.newtimer函数可以创建一个timer定时器。该函数接受一个duration类型的参数,表示定时器的超时时间,并返回一个*timer类型的指针。
看下源码,timer结构体中,timer.c是一个时间类型的通道
package main import ( "fmt" "time" ) func main() { // func newtimer(d duration) *timer timer := time.newtimer(2 * time.second) // 设置超时时间2秒 // 先打印下当前时间 fmt.println("当前时间:", time.now()) //我们可以打印下这个只读通道的值 //timer.c就是我们在定义定时器的时候,存放的时间,等待对应的时间。现在这个就是根据当前时间加2秒 fmt.println("通道里面的值", <-timer.c) }
可以看到timer.c这个只读通道里面的值,就是通过newtimer设置时间间隔后的时间
因此,我们可以根据时间通道,来定时将来某个时间要做的事
package main import ( "fmt" "time" ) func main() { timer := time.newtimer(2 * time.second) // 设置超时时间2秒 <-timer.c //经过两秒后只想下面代码 fmt.println("after 2s time out!") }
在上述代码中,创建了一个超时时间为2秒的定时器,程序会阻塞在<-timer.c处,直到2秒后定时器触发,程序继续执行并打印“after 2s time out!”。
2. 停止timer
使用stop方法可以停止一个timer定时器。该方法返回一个布尔值,表示定时器是否在超时前被停止。
如果返回true,表示定时器在超时前被成功停止;如果返回false,表示定时器已经超时或已经被停止过。
package main import ( "fmt" "time" ) func main() { timer := time.newtimer(2 * time.second) // 设置超时时间2秒 //这里,创建定时器后,里面执行了停止方法,肯定是在定时器超时前停止了,返回true res := timer.stop() fmt.println(res) // 输出:true }
在上述代码中,创建了一个超时时间为2秒的定时器,并立即调用stop方法停止它。由于定时器还没有超时,所以stop方法返回true。
3. 重置timer
对于已经过期或者是已经停止的timer,可以通过reset方法重新激活它,并设置新的超时时间。reset方法也返回一个布尔值,表示定时器是否在重置前已经停止或过期。
package main import ( "fmt" "time" ) func main() { timer := time.newtimer(2 * time.second) <-timer.c fmt.println("time out1") //经过两秒后,定时器超时了 res1 := timer.stop() //此时再stop,得到的是false fmt.printf("res1 is %t\n", res1) // 输出:false //然后我们重置定时器。重置成3秒后超时 timer.reset(3 * time.second) res2 := timer.stop() //此时再stop,由于定时器没超时,得到的是true fmt.printf("res2 is %t\n", res2) // 输出:true }
在上述代码中,首先创建了一个超时时间为2秒的定时器,并在超时后打印“time out1”。
然后调用stop方法停止定时器,由于定时器已经过期,所以stop方法返回false。
接着调用reset方法将定时器重新激活,并设置新的超时时间为3秒。
最后再次调用stop方法停止定时器,由于此时定时器还没有过期,所以stop方法返回true。
4. time.afterfunc
time.afterfunc函数可以接受一个duration类型的参数和一个函数f,返回一个*timer类型的指针。在创建timer之后,等待一段时间d,然后执行函数f。
package main import ( "fmt" "time" ) func main() { duration := time.duration(1) * time.second f := func() { fmt.println("f has been called after 1s by time.afterfunc") } // func afterfunc(d duration, f func()) *timer //等待duration时间后,执行f函数 //这里是经过1秒后。执行f函数 timer := time.afterfunc(duration, f) defer timer.stop() time.sleep(2 * time.second) }
在上述代码中,创建了一个超时时间为1秒的定时器,并在超时后执行函数f。
使用defer语句确保在程序结束时停止定时器。程序会在1秒后打印“f has been called after 1s by time.afterfunc”。
5. time.after
time.after函数会返回一个*timer类型的管道,该管道会在经过指定时间段d后写入数据。调用这个函数相当于实现了一个定时器。
package main import ( "fmt" "time" ) func main() { ch := make(chan string) go func() { time.sleep(3 * time.second) ch <- "test" }() //使用select,哪个先到来,耗时时间短,执行哪个 select { case val := <-ch: fmt.printf("val is %s\n", val) // 这个case是两秒后执行 // func after(d duration) <-chan time case <-time.after(2 * time.second): fmt.println("timeout!!!") } }
在上述代码中,创建了一个管道ch,并在另一个goroutine中等待3秒后向管道写入数据。
在主goroutine中使用select语句监听两个管道:一个是刚刚创建的ch,另一个是time.after函数返回的管道c。
由于ch管道3秒后才会有数据写入,而time.after函数是2秒超时,所以2秒后select会先收到管道c里的数据,执行“timeout!!!”并退出。
二、ticker定时器
ticker定时器可以周期性地不断触发时间事件,不需要额外的reset操作。ticker的结构中也包含一个time类型的管道c,每隔固定时间段d就会向该管道发送当前的时间,根据这个管道消息来触发事件。
ticker定时器是go标准库time包中的一个重要组件。它允许你每隔一定的时间间隔执行一次指定的操作。ticker定时器在创建时会启动一个后台goroutine,该goroutine会按照指定的时间间隔不断向一个通道(channel)发送当前的时间值。
1. 创建ticker
要创建一个ticker定时器,你可以使用time.newticker函数。这个函数接受一个time.duration类型的参数,表示时间间隔,并返回一个*time.ticker类型的指针。
ticker定时器的核心是一个通道(channel),你可以通过监听这个通道来接收时间间隔到达的事件。
ticker := time.newticker(1 * time.second)
上面的代码创建了一个每隔1秒触发一次的ticker定时器。
2. 监听ticker事件
要监听ticker定时器的事件,你可以使用range关键字或者select语句来监听ticker定时器的通道。每次时间间隔到达时,ticker定时器的通道都会接收到一个当前的时间值。
for range ticker.c { // 在这里执行周期性任务 }
package main import ( "fmt" "time" ) func main() { ticker := time.newticker(1 * time.second) //查看定时器数据类型 fmt.printf("定时器数据类型%t\n", ticker) //启动协程来监听定时器触发事件,通过time.sleep函数来等待5秒钟,然后调用ticker.stop()函数来停止定时器。 //最后,输出"定时器停止"表示定时器已经成功停止。 go func() { for range ticker.c { fmt.println("ticker ticked") } }() //执行5秒后,让定时器停止 time.sleep(5 * time.second) ticker.stop() fmt.println("定时器停止") }
或者,如果你需要同时监听多个通道,你可以使用select语句:
select { case t := <-ticker.c: // 处理ticker定时器事件 case <-stopchan: // 处理停止信号 }
package main import ( "fmt" "time" ) func main() { ticker := time.newticker(1 * time.second) // 创建一个每秒触发一次的ticker定时器 defer ticker.stop() // 确保在main函数结束时停止定时器 for { select { case t := <-ticker.c: fmt.println("tick at", t) } } }
每秒执行一次
3. 停止ticker定时器
当你不再需要ticker定时器时,你应该调用它的stop方法来停止它。停止ticker定时器可以释放与之关联的资源,并防止不必要的goroutine继续运行。
ticker.stop()
停止ticker定时器后,它的通道将不再接收任何事件。
package main import ( "fmt" "time" ) func main() { ticker := time.newticker(500 * time.millisecond) // 创建一个每500毫秒触发一次的ticker定时器 timeend := make(chan bool) // 用于停止ticker定时器的通道 go func() { for { select { //当达到设置的停止条件式,停止循环 case <-timeend: fmt.println("===结束任务") break case t := <-ticker.c: fmt.println("==500毫秒响应一次:", t) } } }() time.sleep(5 * time.second) // 主线程等待5秒钟 ticker.stop() // 停止ticker定时器 timeend <- true // 发送结束信号 fmt.println("===定时任务结束===") }
持续执行5秒后,定时器停止运行
三、定时器应用案例
1. 定时打印日志
ticker定时器的一个常见应用是定时打印日志。通过设置一个ticker定时器,你可以每隔固定的时间间隔输出一次日志信息,从而监控程序的运行状态。
package main import ( "fmt" "time" ) func main() { ticker := time.newticker(5 * time.second) defer ticker.stop() // 确保程序结束时停止ticker定时器 for range ticker.c { // 打印当前时间作为日志 fmt.println("current time:", time.now()) } }
5秒打印一次
在这个例子中,我们创建了一个每隔5秒触发一次的ticker定时器,并在一个无限循环中监听它的事件。
每次事件触发时,我们都会打印当前的时间作为日志信息。注意,由于我们在main函数中使用了defer语句来确保ticker定时器在程序结束时被停止,所以即使循环是无限的,程序也不会因为ticker定时器而泄漏资源。
然而,在实际应用中,你可能需要在某个条件下提前停止ticker定时器。这时,你可以使用一个额外的通道来发送停止信号:
package main import ( "fmt" "time" ) func main() { ticker := time.newticker(5 * time.second) stopchan := make(chan struct{}) go func() { // 模拟一个运行一段时间的任务 time.sleep(15 * time.second) // 发送停止信号 stopchan <- struct{}{} }() for { select { case t := <-ticker.c: fmt.println("tick at", t) case <-stopchan: fmt.println("ticker stopped") ticker.stop() return } } }
在这个例子中,我们创建了一个额外的stopchan通道来发送停止信号。我们启动了一个goroutine来模拟一个运行一段时间的任务,并在任务完成后向stopchan发送一个停止信号。
在for循环中,我们使用select语句同时监听ticker定时器的通道和stopchan通道。当接收到停止信号时,我们停止ticker定时器并退出程序。
2. 周期性检查系统状态
ticker定时器还可以用于周期性检查系统状态。例如,你可以每隔一段时间检查一次服务器的负载、内存使用情况或数据库连接数等关键指标,并在发现异常时采取相应的措施。
package main import ( "fmt" "math/rand" "time" ) // 模拟检查系统状态的函数 func checksystemstatus() { // 这里可以添加实际的检查逻辑 // 例如:检查cpu使用率、内存使用情况等 // 这里我们随机生成一个0到100之间的数作为模拟结果 status := rand.intn(101) fmt.printf("system status: %d\n", status) // 假设状态大于80表示系统异常 if status > 80 { fmt.println("warning: system status is above normal!") // 这里可以添加处理异常的逻辑 // 例如:发送警报、重启服务等 } } func main() { ticker := time.newticker(10 * time.second) defer ticker.stop() // 确保程序结束时停止ticker定时器 for range ticker.c { checksystemstatus() } }
在这个例子中,我们创建了一个每隔10秒触发一次的ticker定时器,并在一个无限循环中监听它的事件。
每次事件触发时,我们都会调用checksystemstatus函数来模拟检查系统状态。checksystemstatus函数会随机生成一个0到100之间的数作为模拟结果,并根据结果判断是否系统异常。
如果系统异常(即状态大于80),则打印警告信息,并可以在这里添加处理异常的逻辑。
同样地,你可以使用额外的通道来发送停止信号,以便在需要时提前停止ticker定时器。
四、总结
本文详细介绍了go语言中timer和ticker两种定时器的用法,并通过实际案例展示了它们的应用场景。timer定时器适用于需要一次性触发的事件,而ticker定时器适用于需要周期性触发的事件