英文:
Should I close time.After channel if waiting result from goroutine?
问题
让我们假设以下代码:
type resultWrapper struct {
result Result
err error
}
func (e executor) Execute(ctx context.Context) Result {
ch := make(chan ResultWrapper, 1)
go func() {
defer close(ch)
// base also returns type Result
res := e.base.Execute(ctx)
if res.IsError() {
ch <- resultWrapper{
result: &e.defaultResponse,
err: nil,
}
return
}
ch <- wrapper{
result: &res,
err: nil,
}
}()
select {
case <-time.After(e.timeout):
return e.defaultResponse
case res := <-ch:
return res.result
}
}
我的问题是,我是否应该以某种适当的方式关闭由 time.After
创建的通道?如果我通过超时从主 Execute
函数退出,那么调用 e.base.Execute
的 goroutine 会发生什么情况?
关于第一个问题,不需要显式关闭由 time.After
创建的通道。根据 Go 语言的规范,只有在发送方知道没有更多的值要发送时才需要关闭通道。在这种情况下,time.After
会在指定的时间后向通道发送一个值,因此不需要手动关闭它。
关于第二个问题,如果从主 Execute
函数中的超时退出,那么调用 e.base.Execute
的 goroutine 将继续运行,直到它完成或被中断。这是因为 goroutine 是独立于主函数的并发执行的。超时退出只会影响主函数的返回值,不会直接影响其他正在运行的 goroutine。
英文:
Let's assume following code
type resultWrapper struct {
result Result
err error
}
func (e executor) Execute(ctx context.Context) Result {
ch := make(chan ResultWrapper, 1)
go func() {
defer close(ch)
// base also returns type Result
res := e.base.Execute(ctx)
if res.IsError() {
ch <- resultWrapper{
result: &e.defaultResponse,
err: nil,
}
return
}
ch <- wrapper{
result: &res,
err: nil,
}
}()
select {
case <-time.After(e.timeout):
return e.defaultResponse
case res := <-ch:
return res.result
}
}
My question is if I should close created by time.After
channel in some proper way?
And I don't fully understand what will happen with goroutine calling e.base.Execute
if I exit from main Execute
function by timeout?
答案1
得分: 3
time.After()
返回一个只能接收的通道:
func After(d Duration) <-chan Time
该通道无法关闭。引用自规范:内置函数:Close:
对于一个具有核心类型为通道类型的参数
ch
,内置函数close
记录不会再向该通道发送更多的值。如果ch
是一个只能接收的通道,则会报错。
time.After()
的文档也说明了:
直到定时器触发之前,底层的定时器不会被垃圾回收器回收。如果效率是一个问题,可以使用
NewTimer
并在不再需要定时器时调用Timer.Stop
。
因此,你可以使用time.NewTimer()
,并使用Timer.Stop()
来提前“释放”它。
**但是!**由于你正在调用e.base.Execute(ctx)
操作已经支持上下文,最简单和最惯用的解决方案是创建一个带有超时的新派生上下文(使用context.WithTimeout()
):
func (e executor) Execute(ctx context.Context) Result {
ctx, cancel := context.WithTimeout(ctx, e.timeout)
defer cancel()
res := e.base.Execute(ctx)
if res.IsError() {
return e.defaultResponse,
}
return res.result
}
如果e.base.Execute()
被正确实现,它必须监视传递的上下文,在e.timeout
之后将被取消。请注意延迟的cancel()
调用,如果e.base.Execute()
在e.timeout
之前完成,它将释放已使用的资源。
英文:
time.After()
returns a receive-only channel:
func After(d Duration) <-chan Time
Which cannot be closed. Quoting from Spec: Built-in functions: Close:
> For an argument ch
with a core type that is a channel, the built-in function close
records that no more values will be sent on the channel. It is an error if ch
is a receive-only channel.
Documentation of time.After()
also states:
> The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.
So you could use time.NewTimer()
which you can "free" earlier using Timer.Stop()
.
But! Since the operation you're calling e.base.Execute(ctx)
already supports a context, the easiest and most idiomatic solution would be to create a new, derived context with timeout (using context.WithTimeout()
):
func (e executor) Execute(ctx context.Context) Result {
ctx, cancel := context.WithTimeout(ctx, e.timeout)
defer cancel()
res := e.base.Execute(ctx)
if res.IsError() {
return e.defaultResponse,
}
return res.result
}
If e.base.Execute()
is properly implemented, it must monitor the passed context, which will be cancelled after e.timeout
. Note the deferred cancel()
call which will free used resources if e.base.Execute()
should finish before e.timeout
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论