如何安排运行的非阻塞功能

My questions is how to schedule running independent non-blocking functions every interval N.

My initial approach is to use go channels within a select statement to receive the values in a non-blocking manner and use time.Sleep(N) in each function to schedule the call.

In the code snippet below, this only for the first run; however, after the first call, it keeps calling computeY() repeatedly without respecting the time.Sleep() call.

    package main

    import (
        "fmt"
        "time"
    )

    var (
        x string = ""
        y string = ""
    )

    func computeY(c chan string) {
        time.Sleep(10 * time.Second)
        fmt.Println("I'm in Y")

        y = "this is Y value"
        c <- y
    }

    func computeX(c chan string) {
        time.Sleep(1 * time.Second)

        x = "this is X value"
        c <- x
    }

    func main() {
        xC := make(chan string)
        yC := make(chan string)

        for {
            go computeX(xC)
            go computeY(yC)

            select {
            case x := <-xC:
                fmt.Println(fmt.Sprintf("X: %v, Y: %v", x, y))
            case y := <-yC:
                fmt.Println(fmt.Sprintf("X: %v, Y: %v", x, y))
            }

        }
    }

You are calling both computeX and computeY every iteration of the loop.

Since computeX takes 1s, the for loop iterates once per second and an extra time when yC gets a value.

This means that you're running go computeY at t=0s, t=1s, t=2s, etc.... The first one terminates at t=10s, the second at t=11s, etc...

If you want to make sure you only schedule one computeX and computeY at a time, you need to change your main to something along the lines of:

    go computeX(xC)
    go computeY(yC)
    for {
        select {
        case x = <-xC:
            fmt.Printf("Finished computeX: X: %v, Y: %v
", x, y)
            go computeX(xC)
        case y = <-yC:
            fmt.Printf("Finished computeY: X: %v, Y: %v
", x, y)
            go computeY(yC)
        }
    } 

A few other things to note about your code:

  • x and y are global and assigned in computeX and computeY
  • your channel reads shadow x and y
  • fmt.Println(fmt.Sprintf("...")) is just fmt.Printf("... ")
  • you don't need to initialize strings to "", that's the default value

While @Marc's answer explains your code's problem and shows how to fix it, I will try to give you some patterns on scheduling functions.

Pattern 1: time.Ticker

A Ticker holds a channel that delivers `ticks' of a clock at intervals.

Example:

func Schedule(interval time.Duration,f func()) {
    t:=time.NewTimer(interval)
    go func() {
        for {
            <-t.C
            f()
        }
    }()
 }

Pattern 2: time.AfterFunc

AfterFunc waits for the duration to elapse and then calls f in its own goroutine. It returns a Timer that can be used to cancel the call using its Stop method.

Example:

func Schedule(interval time.Duration,f func()) {
    var wrap func()
    wrap = func() {
        f()
        time.AfterFunc(wrap)
    }
    time.AfterFunc(f)
}

Pattern 1 is more readable and expressive while pattern 2 is more efficient on memory.