如何在context.WithDeadline和简单的定时器之间做出选择?

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

How to decide between a context.WithDeadline or a simple timer?

问题

在Golang中,我对将上下文传递给其他方法和函数的意图还不太了解。我了解上下文的工作原理,如何使用它,如何保存其值,以及它与父上下文的关系和行为 - 我只是不明白为什么要首先使用上下文。

在一个更具体的例子中,这也是这个问题的实际原因,在我工作的公司中,我们发现由于一个边缘情况,会偶尔出现一些非常耗时的查询。

我们决定采取一个明显的解决方案,即在修复根本原因之前,杀死超过5分钟的查询。

运行事务的方法接受一个最初在API调用中初始化的上下文。这个上下文一直传递到事务函数。在那个时刻,我找到了两种解决方案来终止该查询:

1)使用一个新的上下文:

  • 初始化一个新的context.WithTimeout(ctx, time.Duration(5 * time.Minute))
  • 在一个go routine中监视Done通道,并在那里发出信号时终止事务
  • 如果事务及时成功完成,只需cancel上下文并按预期提交事务。

2)使用一个Timer

  • 创建一个持续5分钟的Timer
  • 如果时间到了,终止事务
  • 否则,提交事务。

从逻辑上讲,它们是相同的解决方案,但是何时以及如何决定是使用具有设置截止时间的上下文还是传统的Timer呢?

英文:

In Golang, I am fairly new to the intentions of passing contexts downstream to other methods and functions. I understand how a context works, how it is used, how it holds its values, how it's related to the parent context and their behaviors -- I just don't understand why to use a context in the first place.

In a more specific example, which is the actual reason of this question, in the company I work for, we have identified some very long-running queries that happen every so often due to an edge case.

An obvious solution we decided to take, given our constraints until we invest time to fix the root cause, is to kill the queries that take more than 5 minutes.

The method that runs our transactions accepts a context which is originally initiated in the API call. This context is passed down all the way to the transaction function. At that moment I found 2 solutions to kill that query:

  1. Using a new context:
  • Initiate a new context.WithTimeout(ctx, time.Duration( 5 * time.Minute) )

  • Watch the Done channel in a go routine and kill the transaction when there's a signal there

  • If the transaction finished successfully in a timely manner, just cancel the context and commit the transaction as expected.

  1. Using a Timer:
  • Create a Timer with 5 minutes duration
  • If the time is over, kill the transaction
  • Else, commit the transaction.

Logically speaking, they are the same solution, however, when and how to decide whether to use a context with a set Deadline or a good old Timer?

答案1

得分: 13

答案在于context.Contexttime.Timer如何传递(取消)信号。

context.Context通过Context.Done()方法提供了一个通道,当使用它的goroutine应该终止时,该通道将被关闭。

time.Timer通过Timer.C结构字段提供了一个通道,给定的时间段后,该通道将发送一个值(该值将是当前时间,在这里不重要)。

这里,重点已经突出显示。通道关闭可以被任意数量的goroutine观察到,且可以无限次数。规范:接收操作符:

> 对于一个关闭的通道的接收操作总是可以立即进行,返回元素类型的零值,在之前已经接收到任何已发送的值之后。

因此,Context可以用于向任意数量的goroutine和位置发出取消信号。Timer只能用于向一个目标发出信号,即从其通道接收值的目标。如果有多个客户端正在监听或尝试从其通道接收,只有一个幸运的客户端将接收到它。

此外,如果您正在使用/处理已经支持/期望context.Context的代码,那么就没有问题要选择哪个。Go 1.8还添加了更多的上下文支持。在具有上下文支持的database/sql包中也有重要的添加,包括DB.BeginTx()

> 提供的上下文将在事务提交或回滚之前使用。如果上下文被取消,sql包将回滚事务。如果提供给BeginTx的上下文被取消,Tx.Commit将返回一个错误。

这是context.Context的主要用途:在API边界上携带截止时间和取消信号,并以并发安全的方式完成(因为Context值是不可变的,通道也可以安全地进行并发使用,设计上不会发生数据竞争;更多信息请参考:https://stackoverflow.com/questions/34039229/if-i-am-using-channels-properly-should-i-need-to-use-mutexes/34039377#34039377)。

相关博文:

Go博客:Go并发模式:上下文

在Go中避免或减轻上下文值的陷阱

Dave Cheney: 上下文用于取消

英文:

The answer lies in how the context.Context and the time.Timer deliver the (cancel) signal.

context.Context gives you access to a channel via the Context.Done() method which will be closed when the goroutines using it should terminate.

time.Timer gives you access to a channel in the Timer.C struct field on which a value will be sent after the given time period (that value will be the current time, but not important here).

There, the key points are highlighted. The channel close may be observed by any number of goroutines, and infinite number of times. Spec: Receive operator:

> A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.

So a Context may be used to signal cancelation to arbitrary number of goroutines and places. A Timer can only be used to signal one target, the one who receives the value from its channel. If multiple clients are listening or trying to receive from its channel, only one will be lucky to receive it.

Also if you're using / working with code that already supports / expects context.Context, then it ins't a question which one to use. Go 1.8 also added more context support. There have been significant additions to the database/sql package with context support; including DB.BeginTx():

> The provided context is used until the transaction is committed or rolled back. If the context is canceled, the sql package will roll back the transaction. Tx.Commit will return an error if the context provided to BeginTx is canceled.

This is the primary use of context.Context: to carry a deadline and signal cancelation across API boundaries, and it's done in a concurrent-safe manner (as Context values are immutable, and channels are also safe for concurrent use, data races cannot occur, by design; more on this: https://stackoverflow.com/questions/34039229/if-i-am-using-channels-properly-should-i-need-to-use-mutexes/34039377#34039377).

Related blog posts:

The Go Blog: Go Concurrency Patterns: Context

Pitfalls of context values and how to avoid or mitigate them in Go

Dave Cheney: Context is for cancelation

huangapple
  • 本文由 发表于 2017年2月21日 12:59:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/42359053.html
匿名

发表评论

匿名网友

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

确定