douzong5835 2019-08-27 14: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-27 18: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)
            }
        }
    }()
    

    展开全部

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
编辑
预览

报告相同问题?

手机看
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回
顶部