通过http启动/停止和处理程序自定义调度程序

I want to create a scheduler so that it executes a task every second for example, but also would like to have and http interface to stop/start the scheduler and get more stats/info, after reading more about timers & tickers, channels and gorutines I came out with this:

https://gist.github.com/nbari/483c5b382c795bf290b5

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

var timer *time.Ticker

func scheduler(seconds time.Duration) *time.Ticker {
    ticker := time.NewTicker(seconds * time.Second)
    go func() {
        for t := range ticker.C {
            // do stuff
            fmt.Println(t)
        }
    }()
    return ticker
}

func Start(timer *time.Ticker) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        timer = scheduler(1)
        w.Write([]byte("Starting scheduler"))
    })
}

func Stop(timer *time.Ticker) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        timer.Stop()
        w.Write([]byte("Stoping scheduler"))
    })
}

func main() {
    timer = scheduler(1)
    http.Handle("/start", Start(timer))
    http.Handle("/stop", Stop(timer))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

The above code is working but I have a global "timer" variable, I would like to know if there is a better way to implement this and also a way for handle more than 1 scheduler, currently thinking on probably implementing kind of a container for all the scheduler but would like to have some feedbacks that could help me find clever solutions.

Yes, there is a better way:

You should use channels here, and, as you suggested, some data structure to hold more schedulers.

I came up with this, it's a most basic working example of what I'd do:

package main

import (
    "errors"
    "fmt"
    "sync"
    "time"
)

// a scheduler runs f on every receive on t and exits when receiving from quit is non-blocking
type scheduler struct {
    t    <-chan time.Time
    quit chan struct{}
    f    func()
}

// a schedulerPool holds multiple schedulers
type schedulerPool struct {
    schedulers map[int]scheduler //I used a map here so you can use more clever keys
    counter    int
    mut        sync.Mutex
}

func newPool() *schedulerPool {
    return &schedulerPool{
        schedulers: make(map[int]scheduler),
    }
}

// start adds and starts a new scheduler that will execute f every interval.
// It returns the generated ID for the scheduler, or an error.
func (p *schedulerPool) start(interval time.Duration, f func()) (int, error) {
    p.mut.Lock()
    defer p.mut.Unlock()
    index := p.counter
    p.counter++

    if _, ok := p.schedulers[index]; ok {
        return 0, errors.New("key already in use")
    }

    sched := scheduler{
        t:    time.NewTicker(interval).C,
        quit: make(chan struct{}),
        f:    f,
    }

    p.schedulers[index] = sched
    go func() {
        for {
            select {
            case <-sched.t:
                sched.f()
            case <-sched.quit:
                return
            }
        }
    }()
    return index, nil
}

// stop stops the scheduler with the given ID.
func (p *schedulerPool) stop(index int) error {
    p.mut.Lock()
    defer p.mut.Unlock()

    sched, ok := p.schedulers[index]
    if !ok {
        return errors.New("does not exist")
    }

    close(sched.quit)
    return nil
}

func main() {
    // get a new pool
    pool := newPool()

    // start a scheduler
    idx1, err := pool.start(time.Second, func() { fmt.Println("hello 1") })
    if err != nil {
        panic(err)
    }

    // start another scheduler
    idx2, err := pool.start(time.Second, func() { fmt.Println("hello 2") })
    if err != nil {
        panic(err)
    }

    // wait some time
    time.Sleep(3 * time.Second)

    // stop the second scheduler
    err = pool.stop(idx2)
    if err != nil {
        panic(err)
    }

    // wait some more
    time.Sleep(3 * time.Second)

    // stop the first scheduler
    err = pool.stop(idx1)
    if err != nil {
        panic(err)
    }
}

Check it out on the playground.

Note that there is no guarantee that the first scheduler will run six times in total and the second one three times.

Also note a few other things I've done:

  • Map access has to be synchronized
  • Generating IDs must be synchronized
  • You should check if the key is actually in the map
  • Use channels to start, stop and control schedulers (for example, add a channel beep to have a scheduler dump something to the console (like "beep"))
  • Define a type for the exact signature of function you want to execute, for example type schedFunc func(int, int)

Obviously, you need to wrap that stuff inside of http handlers, probably take the ID or parameters from the querystring, or some other fancy stuff. This is just for demonstration.