Go语言:调度间隔用哪个好?time.Sleep v.s. time.After

Go编程中循环调度任务的执行间隔我们通常采用 time.Sleep或time.After来实现。

写法1:

go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Cancelled", time.Now())
                wg.Done()
                return
            default:
                time.Sleep(time.Second * 10)
                fmt.Println("Invoked", time.Now())
            }
        }
    }(ctx)

写法2:

go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Cancelled", time.Now())
                wg.Done()
                return
            case <-time.After(time.Second * 10):
                fmt.Println("Invoked", time.Now())
            }
        }
    }(ctx)

这两种方式一样吗?那种方式更好呢? 以上两种写法,都可以满足需求。那么是否这两种写法是等效的呢? 当然不是,为了更好的说明它们的差别,请运行下面的这两段测试代码:

var wg sync.WaitGroup
func cancelTaskAfter(interval time.Duration, cancel context.CancelFunc) {
    go func(cancel context.CancelFunc) {
        time.Sleep(interval)
        fmt.Println("Cancell task", time.Now())
        cancel()
        wg.Done()
    }(cancel)
}

func TestTimerTask1(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    cancelTaskAfter(time.Second*25, cancel)
    wg.Add(1)
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Cancelled", time.Now())
                wg.Done()
                return
            default:
                time.Sleep(time.Second * 10)
                fmt.Println("Invoked", time.Now())
            }
        }
    }(ctx)
    wg.Wait()
}

func TestTimerTask2(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    cancelTaskAfter(time.Second*25, cancel)
    wg.Add(1)
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Cancelled", time.Now())
                wg.Done()
                               return
            case <-time.After(time.Second * 10):
                fmt.Println("Invoked", time.Now())
            }
        }
    }(ctx)
    wg.Wait()
}

也许看到这,你大概已经知道这两种写法的不同了。

以下是输出结果

=== RUN   TestTimerTask1
 Invoked 2019-09-22 17:12:07.055392 +0800 CST m=+35.007752073
 Invoked 2019-09-22 17:12:17.057073 +0800 CST m=+45.009375450
 Cancell task 2019-09-22 17:12:22.051311 +0800 CST m=+50.003583680
 Invoked 2019-09-22 17:12:27.060766 +0800 CST m=+55.013010382
 Cancelled 2019-09-22 17:12:27.060804 +0800 CST m=+55.013048342
 --- PASS: TestTimerTask1 (30.01s)
=== RUN   TestTimerTask2
Invoked 2019-09-22 17:11:42.049262 +0800 CST m=+10.001766181
Invoked 2019-09-22 17:11:52.053054 +0800 CST m=+20.005500478
Cancell task 2019-09-22 17:11:57.050145 +0800 CST m=+25.002563035
Cancelled 2019-09-22 17:11:57.05041 +0800 CST m=+25.002827648
--- PASS: TestTimerTask2 (25.00s)

采用Sleep来实现间隔的时候,如果cancel调用后任务协程正好处于sleep过程中,这时任务是无法被及时取消的。

小结 如果你需要任务能够被及时的取消,那你应该采用time.After来控制调用的间隔。

更多知识,欢迎订阅学习 《Go 语言入门到实战》