如何使 time.Tick 立即触发?

huangapple go评论78阅读模式
英文:

How to get time.Tick to tick immediately

问题

我有一个循环,直到作业启动并运行才停止:

ticker := time.NewTicker(time.Second * 2)
defer ticker.Stop()

started := time.Now()
for now := range ticker.C {
    job, err := client.Job(jobID)
    switch err.(type) {
    case DoesNotExistError:
        continue
    case InternalError:
        return err
    }

    if job.State == "running" {
        break
    }

    if now.Sub(started) > time.Minute*2 {
        return fmt.Errorf("timed out waiting for job")
    }
}

在生产环境中运行得很好。唯一的问题是它使得我的测试变慢。它们在完成之前都要等待至少2秒钟。有没有办法让 time.Tick 立即触发?

英文:

I have a loop that iterates until a job is up and running:

ticker := time.NewTicker(time.Second * 2)
defer ticker.Stop()

started := time.Now()
for now := range ticker.C {
	job, err := client.Job(jobID)
	switch err.(type) {
	case DoesNotExistError:
		continue
	case InternalError:
		return err
	}

	if job.State == "running" {
		break
	}

	if now.Sub(started) > time.Minute*2 {
		return fmt.Errorf("timed out waiting for job")
	}
}

Works great in production. The only problem is that it makes my tests slow. They all wait at least 2 seconds before completing. Is there anyway to get time.Tick to tick immediately?

答案1

得分: 67

很遗憾,根据这个链接,Go开发者似乎不会在可预见的未来中添加这样的功能,所以我们必须应对...

有两种常见的使用ticker的方式:

for循环

给定以下代码:

ticker := time.NewTicker(period)
defer ticker.Stop()
for <- ticker.C {
	...
}

使用:

ticker := time.NewTicker(period)
defer ticker.Stop()
for ; true; <- ticker.C {
	...
}

for-select循环

给定以下代码:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    select {
        case <- ticker.C: 
            f()
        case <- interrupt:
            break loop
    }
}

使用:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    f()

    select {
        case <- ticker.C: 
            continue
        case <- interrupt:
            break loop
    }
}

为什么不直接使用time.Tick()

虽然Tick对于不需要关闭Ticker的客户端很有用,但请注意,如果没有关闭它的方法,底层的Ticker将无法被垃圾回收器回收;它会"泄漏"。

https://golang.org/pkg/time/#Tick

英文:

Unfortunately, it seems that Go developers will not add such functionality in any foreseeable future, so we have to cope...

There are two common ways to use tickers:

for loop

Given something like this:

ticker := time.NewTicker(period)
defer ticker.Stop()
for &lt;- ticker.C {
	...
}

Use:

ticker := time.NewTicker(period)
defer ticker.Stop()
for ; true; &lt;- ticker.C {
	...
}

for-select loop

Given something like this:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    select {
        case &lt;- ticker.C: 
            f()
        case &lt;- interrupt:
            break loop
    }
}

Use:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    f()

    select {
        case &lt;- ticker.C: 
            continue
        case &lt;- interrupt:
            break loop
    }
}

Why not just use time.Tick()?

> While Tick is useful for clients that have no need to shut down the Ticker, be aware that without a way to shut it down the underlying Ticker cannot be recovered by the garbage collector; it "leaks".

https://golang.org/pkg/time/#Tick

答案2

得分: 24

以下是要翻译的内容:

ticker := time.NewTicker(period)
for ; true; <-ticker.C {
    ...
}

https://github.com/golang/go/issues/17601

翻译结果:

ticker := time.NewTicker(period)
for ; true; <-ticker.C {
    ...
}

https://github.com/golang/go/issues/17601

英文:
ticker := time.NewTicker(period)
for ; true; &lt;-ticker.C {
    ...
}

https://github.com/golang/go/issues/17601

答案3

得分: 6

Ticker的实际实现在内部相当复杂。但是你可以用goroutine来包装它:

func NewTicker(delay, repeat time.Duration) *time.Ticker {
    ticker := time.NewTicker(repeat)
    oc := ticker.C
    nc := make(chan time.Time, 1)
    go func() {
        nc <- time.Now()
        for tm := range oc {
            nc <- tm
        }
    }()
    ticker.C = nc
    return ticker
}

Ticker的实际实现在内部相当复杂。但是你可以用goroutine来包装它:

func NewTicker(delay, repeat time.Duration) *time.Ticker {
    ticker := time.NewTicker(repeat)
    oc := ticker.C
    nc := make(chan time.Time, 1)
    go func() {
        nc <- time.Now()
        for tm := range oc {
            nc <- tm
        }
    }()
    ticker.C = nc
    return ticker
}
英文:

The actual implementation of Ticker internally is pretty complicated. But you can wrap it with a goroutine:

func NewTicker(delay, repeat time.Duration) *time.Ticker {
	ticker := time.NewTicker(repeat)
	oc := ticker.C
	nc := make(chan time.Time, 1)
	go func() {
		nc &lt;- time.Now()
		for tm := range oc {
			nc &lt;- tm
		}
	}()
	ticker.C = nc
	return ticker
}

答案4

得分: 6

如果您想立即检查作业,请不要将ticker作为for循环中的条件。例如:

ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

started := time.Now()
for {
job, err := client.Job(jobID)
if err == InternalError {
return err
}

if job.State == "running" {
    break
}

now := <-ticker.C
if now.Sub(started) > 2*time.Minute {
    return fmt.Errorf("等待作业超时")
}

}

如果您确实需要检查DoesNotExistError,请确保在ticker之后进行检查,以免出现忙等待的情况。

英文:

If you want to check the job right away, don't use the ticker as the condition in the for loop. For example:

ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

started := time.Now()
for {
	job, err := client.Job(jobID)
	if err == InternalError {
		return err
	}

	if job.State == &quot;running&quot; {
		break
	}

	now := &lt;-ticker.C
	if now.Sub(started) &gt; 2*time.Minute {
		return fmt.Errorf(&quot;timed out waiting for job&quot;)
	}
}

If you do still need to check for DoesNotExistError, you want to make sure you do it after the ticker so you don't have a busy-wait.

答案5

得分: 2

我烹饪了这样的东西

func main() {
	t := time.Now()
	callme := func() {
        // 做更多的事情
		fmt.Println("callme", time.Since(t))
	}
	ticker := time.NewTicker(10 * time.Second)
	first := make(chan bool, 1)
	first <- true
	for {
		select {
		case <-ticker.C:
			callme()
		case <-first:
			callme()
		}
		t = time.Now()
	}
	close(first)
}
英文:

I cooked up something like this

func main() {
	t := time.Now()
	callme := func() {
        // do somethign more
		fmt.Println(&quot;callme&quot;, time.Since(t))
	}
	ticker := time.NewTicker(10 * time.Second)
	first := make(chan bool, 1)
	first &lt;- true
	for {
		select {
		case &lt;-ticker.C:
			callme()
		case &lt;-first:
			callme()
		}
		t = time.Now()
	}
	close(first)
}

答案6

得分: 1

我认为这可能是for-select循环的一个有趣替代方案,特别是当case的内容不是一个简单的函数时:

原始代码如下:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    select {
        case <- ticker.C: 
            f()
        case <- interrupt:
            break loop
    }
}

使用以下代码进行修改:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()
firstTick := false

// 创建一个包装了ticker的函数,使其在第一次立即触发
tickerChan := func() <-chan time.Time {
  if !firstTick {
    firstTick = true
    c := make(chan time.Time, 1)
    c <- time.Now()
    return c
  }

  return ticker.C
}

loop:
for {
    select {
        case <- tickerChan(): 
            f()
        case <- interrupt:
            break loop
    }
}
英文:

I think this might be an interesting alternative for the for-select loop, specially if the contents of the case are not a simple function:

Having:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()

loop:
for {
    select {
        case &lt;- ticker.C: 
            f()
        case &lt;- interrupt:
            break loop
    }
}

Use:

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)

ticker := time.NewTicker(period)
defer ticker.Stop()
firstTick := false

// create a wrapper of the ticker that ticks the first time immediately
tickerChan := func() &lt;-chan time.Time {
  if !firstTick {
    firstTick = true
    c := make(chan time.Time, 1)
    c &lt;- time.Now()
    return c
  }

  return ticker.C
}

loop:
for {
    select {
        case &lt;- tickerChan(): 
            f()
        case &lt;- interrupt:
            break loop
    }
}

答案7

得分: 0

使用Timer而不是Ticker如何?Timer可以以零持续时间启动,然后重置为所需的持续时间值:

timer := time.NewTimer(0)
defer timer.Stop()

for {
    select {
        case <-timer.C:
            timer.Reset(interval)
            job()
        case <-ctx.Done():
            break
    }
}

Timer和Ticker都是Go语言中用于定时任务的工具。它们都可以在指定的时间间隔内重复执行某个任务。不同之处在于,Timer只会执行一次任务,而Ticker会重复执行任务。

在上面的代码中,我们使用Timer来执行任务。首先,我们创建一个Timer对象,并将其持续时间设置为0,这意味着它立即触发。然后,我们使用defer语句在函数结束时停止Timer,以确保资源被正确释放。

在无限循环中,我们使用select语句监听两个channel:timer.C和ctx.Done()。当timer.C的channel接收到值时,表示定时器已经触发,我们重置Timer的持续时间为interval,并执行job()函数。当ctx.Done()的channel接收到值时,表示上下文已经被取消,我们退出循环。

这样,我们就可以使用Timer来实现定时任务的执行。

英文:

How about using Timer instead of Ticker? Timer can be started with zero duration and then reset to the desired duration value:

timer := time.NewTimer(0)
defer timer.Stop()

for {
    select {
	    case &lt;-timer.C:
		    timer.Reset(interval)
            job()
        case &lt;-ctx.Done():
            break
    }
}

</details>



# 答案8
**得分**: 0

你也可以在循环结束时关闭通道

```golang
t := time.NewTicker(period)
defer t.Stop()

for {
    ...
    
    <-t.C
}
英文:

You can also drain the channel at the end of the loop:

t := time.NewTicker(period)
defer t.Stop()

for {
    ...
    
    &lt;-t.C
}

huangapple
  • 本文由 发表于 2015年9月22日 06:35:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/32705582.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定