Should I close time.After channel if waiting result from goroutine?

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

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 &lt;- resultWrapper{
			  result: &amp;e.defaultResponse,
			  err:    nil,
		  }
		  return
	  }
	  ch &lt;- wrapper{
		  result: &amp;res,
		  err:    nil,
	  }
  }()

  select {
  case &lt;-time.After(e.timeout):
	  return e.defaultResponse
  case res := &lt;-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) &lt;-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.

huangapple
  • 本文由 发表于 2023年4月12日 16:34:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75993450.html
匿名

发表评论

匿名网友

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

确定