douzong5835 2019-08-27 22:19 采纳率: 100%
浏览 78
已采纳

在Web应用程序中运行计划的任务

I want to run tasks every 5 minutes to update stats on my website without blocking the HTTP server.

I've just added basic HTTP server logic with an example of a worker. If I added multiple tasks like this, is this considered bad practice or is there a better way?

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)
}
  • 写回答

1条回答 默认 最新

  • dongna1593 2019-08-28 02:19
    关注

    Your implementation is pretty much what scheduled jobs/tasks in Go look like. There are cron-esque libraries that give you more control for tasks, but in most cases a simple goroutine with a loop is sufficient.

    Here are a few more examples in increasing complexity based on what your needs are:


    Run a task forever, waiting 5 seconds in between each run. Cannot be stopped and doesn't take into account the run time of the task. This is the simplest form and also the most common one.

    go func() {
        for {
            task()
            <-time.After(5 * time.Second)
        }
    }()
    

    Same as before except now there is a channel to stop the task if we ever want to. While your implementation lets you stop the task via the Stop() method, the goroutine will remain open since the channel is never closed (and therefore will leak memory).

    // 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
    

    This is the most robust solution of the three. The task can be stopped at any point, and it also takes into account how long the task took to run when it waits to run it again. In the previous examples, if the task took 1 second to run, and you wait an additional 5 seconds, your interval is actually 6 seconds, relative to when the task starts.

    This solution really is only applicable to very long running tasks or if it's critical that your tasks are ran at constant intervals. If the tasks must be ran at constant intervals then you need to take into account the fact that time.After() will wait at LEAST the duration you provide it -- it could end up waiting slightly longer if the CPU is busy with other 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)
            }
        }
    }()
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 is not in the mmseg::model registry。报错,模型注册表找不到自定义模块。
  • ¥15 安装quartus II18.1时弹出此error,怎么解决?
  • ¥15 keil官网下载psn序列号在哪
  • ¥15 想用adb命令做一个通话软件,播放录音
  • ¥30 Pytorch深度学习服务器跑不通问题解决?
  • ¥15 部分客户订单定位有误的问题
  • ¥15 如何在maya程序中利用python编写领子和褶裥的模型的方法
  • ¥15 Bug traq 数据包 大概什么价
  • ¥15 在anaconda上pytorch和paddle paddle下载报错
  • ¥25 自动填写QQ腾讯文档收集表