【问题标题】:How to use time.After() instead of time.Sleep() to obtain interruptible pause如何使用 time.After() 而不是 time.Sleep() 来获得可中断的暂停
【发布时间】:2017-10-09 00:51:33
【问题描述】:

我有一个程序,它定期检查外部邮箱是否有消息,并有一个用户视图,允许他们查看消息并终止程序。

剥离到最小的功能看起来像这样

package main

import (
    "log"
    "time"
)

func main() {
    log.Println("Hello, playground")

    quit := make(chan bool)
    data := make(chan string)

    go func() {
        for {
            select {
            case <-quit:
                log.Println("Quitting")
                close(data)
                return
            case data <- fetch():
                // Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
                interval := time.Second * 5
                now := time.Now()
                time.Sleep(now.Truncate(interval).Add(interval).Sub(now))
            }
        }
    }()

    go func() {
        time.Sleep(12 * time.Second) // actually user presses a "quit" button
        quit <- true
    }()

loop:
    for {
        select {
        case info, ok := <-data:
            if !ok {
                break loop
            }
            log.Println("Fetched", info)
        }
    }

    log.Println("Goodbye, playground")
}

func fetch() string {
    log.Println("Fetching")
    return "message"
}

你可以run this in the Go Playground

输出是

2009/11/10 23:00:00 Hello, playground
2009/11/10 23:00:00 Fetching
2009/11/10 23:00:00 Fetched message
2009/11/10 23:00:05 Fetching
2009/11/10 23:00:05 Fetched message
2009/11/10 23:00:10 Fetching
2009/11/10 23:00:10 Fetched message
2009/11/10 23:00:15 Fetching
2009/11/10 23:00:15 Quitting
2009/11/10 23:00:15 Goodbye, playground

注意

  • "23:00:15 Fetching" 是我没想到的。
  • 程序在 23:00:15 退出,而不是在 23:00:12,因为它正在休眠。

后者在我的程序中会成为一个问题,因为它在检查消息之间使用了 10 分钟的睡眠时间。将退出延迟那么长时间会使程序看起来非常迟钝。

this answer了解到,你可以使用time.After()创建一个可以中断的循环延迟。

我应该如何最好地将它应用到我的程序中?

【问题讨论】:

    标签: go concurrency sleep scheduler channel


    【解决方案1】:

    “23:00:15 Fetching”是我没想到的。

    这并不奇怪,这是预期的工作。引用Spec: Select statements:

    “选择”语句的执行分几个步骤进行:

    1. 对于语句中的所有情况,在输入“select”语句时,接收操作的通道操作数以及发送语句的通道和右侧表达式仅按源顺序计算一次。

    [...]

    所以select 语句在决定继续/执行哪个分支之前评估通信操作。

    这意味着

    case data <- fetch():
    

    fetch() 将被调用,即使在data 上发送是不可能的,即使从quit 接收可以立即进行。

    由于您在 case 分支之一中处于睡眠状态,因此 quit 准备好接收并不重要,检查(并可选择决定继续该分支)是否必须等到 @987654331 @——整个case分支——完成。

    所以通信操作应该是来自time.After()返回的通道的接收操作,并且只在这个case分支的body中调用fetch()

    你可以让它像这样工作:

    for {
        // Wait until e.g. exactly 0,10,20,30,40 or 50 mins past the hour
        interval := time.Second * 5
        now := time.Now()
        delay := now.Truncate(interval).Add(interval).Sub(now)
    
        select {
        case <-quit:
            log.Println("Quitting")
            close(data)
            return
        case <-time.After(delay):
            data <- fetch()
        }
    }
    

    现在是输出(在Go Playground 上试试):

    2009/11/10 23:00:00 Hello, playground
    2009/11/10 23:00:05 Fetching
    2009/11/10 23:00:05 Fetched message
    2009/11/10 23:00:10 Fetching
    2009/11/10 23:00:10 Fetched message
    2009/11/10 23:00:12 Quitting
    2009/11/10 23:00:12 Goodbye, playground
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-01
      相关资源
      最近更新 更多