英文:
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:
- Using a new context:
-
Initiate a new
context.WithTimeout(ctx, time.Duration( 5 * time.Minute) )
-
Watch the
Done
channel in ago 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.
- 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.Context
和time.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)。
相关博文:
英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论