标签:Context 取消 ctx golang Perform API context 函数
介绍
以一个简单的问题开始. 有一个每秒进行一些处理动作的函数
func Perform() { for { SomeFunction() time.Sleep(time.Second) } }
我们这样运行它
go Perform()
有个想法是在有明确的取消动作或者超过截至时间时,取消这个函数。Context就是为了这个目的而设计的,让我们看一下context.Context接口:
type Context interface { Done() <-chan struct{} Err() error Deadline() (deadline time.Time, ok bool) Value(key interface{}) interface{} }
分析一下:
- ctx.Done() 返回一个取消信号的channel,用于判断这个context是不是被取消了。
- ctx.Err() 返回取消的原因(超过截至时间,或者是取消信号)。
- ctx.Deadline() 返回截至时间,如果被设置的话。
- ctx.Value(key) 返回对应key的值。
那么有几个问题:
为什么 ctx.Done()返回一个channel?为什么不返回一个bool类型的值?
为什么没有cancel方法?
怎样设置deadline?
ctx.Value(key)是干什么的?
为了理解这些API,了解它被设计成满足以下两个要求的原因是非常有用的:
1. 取消应该是建议性的
每个函数自行return是它自己的工作,且调用者不知道被调用函数的内部情况,因此不应该强行终止被调用函数的执行,而应该通知被调用函数不再需要它的工作了。
调用者发送取消信息,让被调用函数决定怎么进一步处理。例如,当函数获知它的工作是不再被需要的时候,函数可以自清理并提前返回 。
2. 取消信号应该是可传递的
当取消一个函数, 我们也需要取消它调用的所有函数。这意味着取消信号应该被广播,从调用者到它各个层级的子函数。
创建一个context
一个简单的方法,用context.Background()创建一个context:
ctx := context.Background()
context.Background() 返回一个空的context。由于取消是建议性和可传递的,我们应该给每个函数一个代表取消信息的参数。
我们修改一下程序:
go Perform()
变成
ctx := context.Background() go Perform(ctx)
设置deadline
空context是没用的。我们需要设置deadline或者能够手动取消它。然而,context.Context 接口只定义了查询方法。我们无法修改deadline。
我们不能修改context的原因是:我们希望阻止带context参数的函数去修改或者取消一个请求。
context中信息流的方向是严格的从父函数流向子函数的。例如,当用户关闭浏览器中的一个标签(代表父), 隶属这个标签的所有函数(代表子)都应该被关闭。
因此,我们得到一个被设置了截止日期的上下文:
ctx, cancel := context.WithDeadline(parentContext, time) 或者 ctx, cancel := context.WithTimeout(parentContext, duration)
请注意,cancel作为单独的值返回。因为如果ctx有取消方法,那么子函数就可以取消它。说明同样,API也严格限制取消的方向,只能从父到子。
在我们需要从子函数取消请求的特殊情况下,我们必须明确地将cancel函数作为单独的参数传递。(这边想到个题外话: 如何优雅地关闭Go channel )
继续我们的例子:
ctx, cancel := context.WithTimeout(context.Background(), time.Hour) go Perform(ctx)
我们可以使用cancel()来提醒Perform函数,我们不再需要它的工作了。下面,我们将看Perform如何处理此信号。
检查context是否被取消
取消事件应该向下广播到所有被调用的函数。Go中channel有一个特性,使它很适合这个目的:从一个已被关闭的channel接收,会立即返回零值。
这意味着多个函数可以监听同一个channel,当它关闭时,所有函数都能接收到取消信号。
Done方法返回在一个只读通道(cancel函数的功能就是关闭这个channel)。这有一个检查context是否被取消的简单示例:
func Perform(ctx context.Context) { for { SomeFunction() select { case <-ctx.Done(): // ctx is canceled return default: // ctx is not canceled, continue immediately } } }
请注意,select语句不会阻塞,因为它有一个default语句。这会导致for循环立即执行SomeFunction。我们需要在每次迭代之间休眠1秒钟:
func Perform(ctx context.Context) { for { SomeFunction() select { case <-ctx.Done(): // ctx is canceled return case <-time.After(time.Second): // wait for 1 second } } }
当context被取消时,我们可以通过调用ctx.Err()找出原因。
func Perform(ctx context.Context) error { for { SomeFunction() select { case <-ctx.Done(): return ctx.Err() case <-time.After(time.Second): // wait for 1 second } } return nil }
ctx.Err()只有两个可能的值:context.DeadlineExceeded和context.Canceled。ctx.Err()只有在 ctx.Done()被关闭后才会被调用。ctx被取消之前的ctx.Err()的结果是未被设置的。
如果SomeFunction会执行很长时间,我们也可以让它知道取消信号。我们通过将ctx作为其一个参数传递给它来实现这一点。
func Perform(ctx context.Context) error { for { SomeFunction(ctx) select { case <-ctx.Done(): return ctx.Err() case <-time.After(time.Second): // wait for 1 second } } return nil }
context.TODO()是什么?
和context.Background相似,创建context的另一个方法是:
ctx := context.TODO()
TODO函数也返回一个空的context。TODO被用于重构函数去支持context。当在函数中无法获取一个父context时,我们使用它 . 所有的TODO context最终都应该被其他的context替换掉。
ctx.WithValue是什么?
context的最常见用法是处理请求中的取消。为实现这一点,context通常贯穿请求的整个生命周期(例如,作为所有函数的第一个参数)。
贯穿请求的生命周期中的另一个有用信息是数据的值:诸如用户会话和登录信息之类。context包也可以轻松地将这些值存储在context实例中。因为它们与取消信息共享相同的调用路径。
要设置数据的值,我们使用context.WithValue函数派生一个context:
ctx := context.WithValue(parentContext, key, value)
要从ctx或从ctx派生的任何context中检索此值,请使用:
value := ctx.Value(key)
相关资料
对于想要了解上下文包的人,我强烈推荐以下两个链接。
- Cancellation, Context, and Plumbing (video) by Sameer Ajmani
- Pipelines and cancellation (blog post) by Sameer Ajmani
标签:Context,取消,ctx,golang,Perform,API,context,函数 来源: https://www.cnblogs.com/adarking/p/10533052.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。