英文:
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 <- result
}()
return resChan
}
and
c) then block the main goroutine on a select statement as follows
select {
case err := <-ctx.Done():
log.Println("Process has timed out...exiting")
os.Exit(err)
case result := <- resChan
log.Println("Process completed in time")
}
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
这比必要的要复杂。有两种方法可以做到这一点:
-
如果函数接受一个
context.Context
参数,你不需要在顶层等待<-ctx.Done()
。相反,当上下文完成时,函数将会(在一段未知的延迟后,可能永远不会,这取决于函数)返回ctx.Err()
(应该是context.DeadlineExceeded
)。 -
如果函数不接受
context.Context
参数,那么你可以直接退出进程。你不需要使用context.Context
来做这个。我所指的只是context.Context
只是一种更复杂的调用<-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 <- resultStruct{v, err}
}()
// 等待超时或结果。
select {
case <-time.After(timeout):
log.Println("超时")
os.Exit(1)
case result := <-ch:
log.Println("成功:", result)
}
}
请注意,这种策略之所以有效,是因为你调用了 os.Exit()
,它会强制终止所有未完成的 goroutine。
英文:
This is more complicated than it needs to be. There are two ways to do this:
-
If the functions take a
context.Context
argument, you don't need to wait for<-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) returnctx.Err()
(which should becontext.DeadlineExceeded
). -
If the functions do not take a
context.Context
argument, then you can just exit the process. You don't need acontext.Context
to do this. All I mean by that is that thecontext.Context
is just a more complicated way to call<-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 <- resultStruct{v, err}
}()
// Wait for either the timeout, or the result.
select {
case <-time.After(timeout):
log.Println("Timed out")
os.Exit(1)
case result := <-ch:
log.Println("Success:", result)
}
}
Note that this strategy only works because you are calling os.Exit()
, which forcibly terminates all outstanding goroutines.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论