【Golang】使用 - 限时调用一个函数(超时退出)

Posted by 西维蜀黍 on 2020-11-24, Last Modified on 2021-09-21

使用 time.After

package main

import (
   "fmt"
   "time"
)

func main() {

   c1 := make(chan string, 1)
   go func() {
      fmt.Printf("before sleep: %v\n", time.Now())
      time.Sleep(2 * time.Second)
      c1 <- "result 1"
      // Never go to here
      fmt.Printf("after sleep: %v\n", time.Now())
   }()

   fmt.Printf("before select: %v\n", time.Now())
   select {
   case res := <-c1:
      fmt.Println(res)
   case <-time.After(1 * time.Second):
      fmt.Println("timeout 1")
   }
   fmt.Printf("after select: %v\n", time.Now())
}

Output:

before select: 2020-11-24 22:57:40.129712 +0800 +08 m=+0.000122644
before sleep: 2020-11-24 22:57:40.129721 +0800 +08 m=+0.000131671
timeout 1
after select: 2020-11-24 22:57:41.13308 +0800 +08 m=+1.003459705

For our example, suppose we’re executing an external call that returns its result on a channel c1 after 2s. Note that the channel is buffered, so the send in the goroutine is nonblocking. This is a common pattern to prevent goroutine leaks in case the channel is never read.

Here’s the select implementing a timeout. res := <-c1 awaits the result and <-time.After awaits a value to be sent after the timeout of 1s. Since select proceeds with the first receive that’s ready, we’ll take the timeout case as the operation (we use hardcode time.Sleep(2 * time.Second) here) takes more than the allowed 1s.

同时考虑 context deadline

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	AsyncCall()
}

func AsyncCall() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
	defer cancel()
	go func(ctx context.Context) {
		fmt.Print("sss")
	}(ctx)

	select {
	case <-ctx.Done():
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(time.Millisecond * 900)):
		fmt.Println("timeout!!!")
		return
	}
}

说明

1、通过context的WithTimeout设置一个有效时间为800毫秒的context。

2、该context会在耗尽800毫秒后或者方法执行完成后结束,结束的时候会向通道ctx.Done发送信号。

3、有人可能要问,你这里已经设置了context的有效时间,为什么还要加上这个time.After呢?

这是因为该方法内的context是自己申明的,可以手动设置对应的超时时间,但是在大多数场景,这里的ctx是从上游一直传递过来的,对于上游传递过来的context还剩多少时间,我们是不知道的,所以这时候通过time.After设置一个自己预期的超时时间就很有必要了。

4、注意,这里要记得调用cancel(),不然即使提前执行完了,还要傻傻等到800毫秒后context才会被释放。

Reference