在 Go 中,可以使用 time 包来实现类似于 JavaScript 中的 setTimeout 和 setInterval 的功能。time 包提供了一个 Timer 结构体,用于在指定时间后调用一次函数,以及一个 Ticker 结构体,用于每隔指定时间调用一次函数。
setTimeout 实现
要实现 setTimeout 的功能,可以使用 time.AfterFunc 函数。该函数接受一个时间间隔和一个函数作为参数,并在指定时间间隔后调用该函数。以下是一个例子:
package main
import (
"fmt"
"time"
)
func setTimeout(callback func(), delay time.Duration) *time.Timer {
return time.AfterFunc(delay, callback)
}
func main() {
t1 := setTimeout(func() {
fmt.Println("3 seconds have passed")
}, 3*time.Second)
t2 := setTimeout(func() {
fmt.Println("10 seconds have passed")
}, 10*time.Second)
time.Sleep(5 * time.Second)
t1.Stop()
t2.Stop()
time.Sleep(10 * time.Second)
}
在这段代码中,我们定义了一个 setTimeout 函数,它接受一个回调函数和一个时间间隔作为参数。在函数体内,我们使用 time.AfterFunc 函数来设置定时器,当时间到达时,就会执行回调函数。
setInterval 实现
要实现 setInterval 的功能,可以使用 time.Tick 函数和一个 for 循环。time.Tick 函数接受一个时间间隔作为参数,并返回一个 channel。通过在 for 循环中遍历该 channel 可以每隔指定时间调用一次函数。以下是一个例子:
package main
import (
"fmt"
"time"
)
func myFunc() {
fmt.Println("Hello, world!")
}
func main() {
ticker := time.Tick(1 * time.Second)
for range ticker {
myFunc()
}
}
上面的代码会每隔 1 秒调用 myFunc 函数,并输出 "Hello, world!"。
但上述代码还存在一个问题:for 循环会阻塞 main 主进程执行,要解决这个问题,可以在另一个 goroutine 中运行 for 循环,以便将控制权还给主函数。以下是修改后的代码:
package main
import (
"fmt"
"time"
)
func myFunc() {
fmt.Println("Hello, world!")
}
func main() {
ticker := time.Tick(1 * time.Second)
go func() {
for range ticker {
myFunc()
}
}()
fmt.Println("主逻辑继续执行 --->")
_, err := fmt.Scanln() // 防止程序立即退出
if err != nil {
return
}
}
该 goroutine 将在程序运行期间一直运行,直到主函数接收到输入时退出。
如果要实现一个完整的 setInterval 函数功能,可以这么实现:
type IntervalHandler struct {
ticker *time.Ticker
stop chan bool
}
func setInterval(callback func(), interval time.Duration) *IntervalHandler {
ticker := time.NewTicker(interval)
stop := make(chan bool)
go func() {
for {
select {
case <-ticker.C:
callback()
case <-stop:
ticker.Stop()
return
}
}
}()
return &IntervalHandler{
ticker,
stop,
}
}
func (h *IntervalHandler) Stop() {
h.stop <- true
}
在这个示例中,我们定义了一个 IntervalHandler 结构体,其中包含定时器的 Ticker 和 stop 停止信号。在 setInterval 函数中,我们创建了一个定时器,并启动了一个协程,该协程会反复执行回调函数,直到接收到停止信号为止。最后,我们将 IntervalHandle 结构体的指针作为句柄返回。
使用 IntervalHandler 结构体的 Stop 方法可以停止定时器的循环执行,如下所示:
handler := setInterval(func() {
fmt.Println("Tick!")
}, 1*time.Second)
time.Sleep(10 * time.Second)
handler.Stop()
在这个示例中,我们首先调用 setInterval 函数创建一个定时器,并将返回的句柄保存在 handler 变量中。然后,我们等待 10 秒钟,最后调用 handler.Stop() 方法停止定时器的循环执行。注意,停止定时器的操作必须在 setInterval 函数的协程中执行,否则会引发 panic 错误。