运行 Web 应用程序中的计划任务
Run scheduled tasks within a web application
我想 运行 每 5 分钟执行一次任务,以在不阻止 HTTP 服务器的情况下更新我网站上的统计信息。
我刚刚添加了带有工作人员示例的基本 HTTP 服务器逻辑。如果我像这样添加多个任务,这被认为是不好的做法还是有更好的方法?
package main
import (
"fmt"
"net/http"
"time"
)
func Home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home page")
}
func schedule(f func(), interval time.Duration) *time.Ticker {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
f()
}
}()
return ticker
}
func longRunningTask() {
fmt.Println("Long running task")
}
func main() {
schedule(longRunningTask, time.Second*5)
http.HandleFunc("/", Home)
http.ListenAndServe("127.0.0.1:8080", nil)
}
您的实现与 Go 中计划的 jobs/tasks 非常相似。有一些类似 cron 的库可以让您更好地控制任务,但在大多数情况下,一个简单的带有循环的 goroutine 就足够了。
以下是根据您的需要增加复杂性的更多示例:
运行 一个永远的任务,在每个 运行 之间等待 5 秒。无法停止且不考虑任务的 运行 时间。这是最简单的形式,也是最常见的形式。
go func() {
for {
task()
<-time.After(5 * time.Second)
}
}()
与以前相同,只是现在有一个通道可以在我们需要时停止任务。虽然您的实现允许您通过 Stop()
方法停止任务,但 goroutine 将保持打开状态,因为通道永远不会关闭(因此会泄漏内存)。
// Includes a channel to stop the task if needed.
quit := make(chan bool, 1)
go func() {
task()
for {
select {
case <-quit:
return
case <-time.After(5 * time.Second):
task()
}
}
}()
// To stop the task
quit <- true
这是三个解决方案中最可靠的解决方案。任务可以随时停止,它还会考虑任务在等待再次 运行 时花了多长时间 运行。在前面的例子中,如果任务用了 1 秒到 运行,而你又等了 5 秒,相对于任务开始的时间,你的间隔实际上是 6 秒。
此解决方案实际上仅适用于非常长的 运行ning 任务,或者如果您的任务 运行 间隔固定很重要。如果任务必须 运行 以恒定的间隔进行,那么您需要考虑这样一个事实,即 time.After()
将至少等待您提供的持续时间——如果 CPU 正忙于其他 processes/goroutines。
// Calls `fn` and then waits so the total elapsed time is `interval`
func runAndWait(interval time.Duration, fn func()) {
earlier := time.Now()
fn()
diff := time.Now().Sub(earlier)
<-time.After(interval - diff)
}
quit := make(chan bool, 1)
go func() {
// The total time to run one iteration of the task
interval := 5 * time.Second
for {
select {
case <-quit:
return
default:
runAndWait(interval, task)
}
}
}()
我想 运行 每 5 分钟执行一次任务,以在不阻止 HTTP 服务器的情况下更新我网站上的统计信息。
我刚刚添加了带有工作人员示例的基本 HTTP 服务器逻辑。如果我像这样添加多个任务,这被认为是不好的做法还是有更好的方法?
package main
import (
"fmt"
"net/http"
"time"
)
func Home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home page")
}
func schedule(f func(), interval time.Duration) *time.Ticker {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
f()
}
}()
return ticker
}
func longRunningTask() {
fmt.Println("Long running task")
}
func main() {
schedule(longRunningTask, time.Second*5)
http.HandleFunc("/", Home)
http.ListenAndServe("127.0.0.1:8080", nil)
}
您的实现与 Go 中计划的 jobs/tasks 非常相似。有一些类似 cron 的库可以让您更好地控制任务,但在大多数情况下,一个简单的带有循环的 goroutine 就足够了。
以下是根据您的需要增加复杂性的更多示例:
运行 一个永远的任务,在每个 运行 之间等待 5 秒。无法停止且不考虑任务的 运行 时间。这是最简单的形式,也是最常见的形式。
go func() {
for {
task()
<-time.After(5 * time.Second)
}
}()
与以前相同,只是现在有一个通道可以在我们需要时停止任务。虽然您的实现允许您通过 Stop()
方法停止任务,但 goroutine 将保持打开状态,因为通道永远不会关闭(因此会泄漏内存)。
// Includes a channel to stop the task if needed.
quit := make(chan bool, 1)
go func() {
task()
for {
select {
case <-quit:
return
case <-time.After(5 * time.Second):
task()
}
}
}()
// To stop the task
quit <- true
这是三个解决方案中最可靠的解决方案。任务可以随时停止,它还会考虑任务在等待再次 运行 时花了多长时间 运行。在前面的例子中,如果任务用了 1 秒到 运行,而你又等了 5 秒,相对于任务开始的时间,你的间隔实际上是 6 秒。
此解决方案实际上仅适用于非常长的 运行ning 任务,或者如果您的任务 运行 间隔固定很重要。如果任务必须 运行 以恒定的间隔进行,那么您需要考虑这样一个事实,即 time.After()
将至少等待您提供的持续时间——如果 CPU 正忙于其他 processes/goroutines。
// Calls `fn` and then waits so the total elapsed time is `interval`
func runAndWait(interval time.Duration, fn func()) {
earlier := time.Now()
fn()
diff := time.Now().Sub(earlier)
<-time.After(interval - diff)
}
quit := make(chan bool, 1)
go func() {
// The total time to run one iteration of the task
interval := 5 * time.Second
for {
select {
case <-quit:
return
default:
runAndWait(interval, task)
}
}
}()