使用上下文在连续的函数调用中共享一个公共的超时时间

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

Using context to share a common timeout across consecutive function calls

问题

我想在Go程序中进行一些连续的函数调用,例如:

(显然省略了错误检查)

result1, err := fxn1()

result2, err := fxn2()

我希望整个调用序列在一定的时间内完成,否则进程应该被中止(并以错误退出程序)。

假设我将超时时间(作为持续时间)传递给我的程序,我猜一种方法是:

a) 使用截止时间创建一个上下文,将超时时间添加到当前时间中

myDeadline := time.Now().Add(timeout * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), myDeadline)
defer cancel()

b) 让最终的函数通过通道传递其结果(我认为这被称为通道生成器模式?)

func fxn2() (chan string) {

 resChan := make(chan string)

   go func() {
   // 完成任务
   resChan <- result
   }()
 return resChan
}

然后

c) 在主goroutine上使用select语句阻塞,如下所示

select {
  case err := <-ctx.Done():
    log.Println("进程已超时...退出")
    os.Exit(err)
  case result := <- resChan:
    log.Println("进程在规定时间内完成")
}

这是我目前能想到的最好的方法,但我想知道是否有更好或更符合Go习惯的方法(例如为每个函数生成新的上下文副本-当然应该接受context.Context作为输入参数-以某种方式跟踪剩余时间?)

英文:

I want to make some consecutive function calls in a Go program, e.g.

(error checking apparently ommitted)

result1, err := fxn1()

result2, err := fxn2()

I want the entire call sequence to be completed within a certain duration, otherwise the process should be aborted (and the program exited with error)

Assuming I have the timeout (as duration) somehow passed into my program, I guess one approach is to

a) create a context with Deadline adding timeout to current time

myDeadline := time.Now().Add(timeout * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), myDeadline)
defer cancel()

b) have the final function communicate its result via a channel (I think this is called the channel generator pattern?)

func fxn2() (chan string) {

 resChan := make(chan string)

   go func() {
   // get the job done
   resChan &lt;- result
   }()
 return resChan
}

and

c) then block the main goroutine on a select statement as follows

select {
  case err := &lt;-ctx.Done():
    log.Println(&quot;Process has timed out...exiting&quot;)
    os.Exit(err)
  case result := &lt;- resChan
    log.Println(&quot;Process completed in time&quot;)
}

This is the best I can think of for now, but I was wandering whether there is a better or more go-idiomatic way (say with spanwing new copies of contexts for each function - that should of course accept context.Context as input arguments) that somehow track the remaining time?)

答案1

得分: 3

这比必要的要复杂。有两种方法可以做到这一点:

  1. 如果函数接受一个 context.Context 参数,你不需要在顶层等待 &lt;-ctx.Done()。相反,当上下文完成时,函数将会(在一段未知的延迟后,可能永远不会,这取决于函数)返回 ctx.Err()(应该是 context.DeadlineExceeded)。

  2. 如果函数不接受 context.Context 参数,那么你可以直接退出进程。你不需要使用 context.Context 来做这个。我所指的只是 context.Context 只是一种更复杂的调用 &lt;-time.After(timeout) 的方式。

type myResultType struct{}

func longFunctionCall() (*myResultType, error) {
    ...
}

func invokeFunctionWithDeadline() {
	const timeout = 10 * time.Second

    // 在一个 goroutine 上运行函数,并使用通道返回结果。
	type resultStruct struct {
		value *myResultType
		err error
	}
	ch := make(chan resultStruct)
	go func() {
		v, err := longFunctionCall()
		ch &lt;- resultStruct{v, err}
	}()

    // 等待超时或结果。
	select {
	case &lt;-time.After(timeout):
		log.Println("超时")
		os.Exit(1)
	case result := &lt;-ch:
		log.Println("成功:", result)
	}
}

请注意,这种策略之所以有效,是因为你调用了 os.Exit(),它会强制终止所有未完成的 goroutine。

英文:

This is more complicated than it needs to be. There are two ways to do this:

  1. If the functions take a context.Context argument, you don't need to wait for &lt;-ctx.Done() at the top level. Instead, when the context finishes, the functions will (after some unknown unknown delay, possibly never, depending on the function) return ctx.Err() (which should be context.DeadlineExceeded).

  2. If the functions do not take a context.Context argument, then you can just exit the process. You don't need a context.Context to do this. All I mean by that is that the context.Context is just a more complicated way to call &lt;-time.After(timeout).

type myResultType struct{}

func longFunctionCall() (*myResultType, error) {
    ...
}

func invokeFunctionWithDeadline() {
	const timeout = 10 * time.Second

    // Run the function on a goroutine, and return the result with a channel.
	type resultStruct struct {
		value *myResultType
		err error
	}
	ch := make(chan resultStruct)
	go func() {
		v, err := longFunctionCall()
		ch &lt;- resultStruct{v, err}
	}()

    // Wait for either the timeout, or the result.
	select {
	case &lt;-time.After(timeout):
		log.Println(&quot;Timed out&quot;)
		os.Exit(1)
	case result := &lt;-ch:
		log.Println(&quot;Success:&quot;, result)
	}
}

Note that this strategy only works because you are calling os.Exit(), which forcibly terminates all outstanding goroutines.

huangapple
  • 本文由 发表于 2021年8月12日 04:04:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/68748342.html
匿名

发表评论

匿名网友

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

确定