context
是Go
并发编程中常用到一种编程模式。本文将从为什么需要context
,深入了解context的实现原理,以了解如何使用context
。
作者:Turling_hu来源: 掘金
这篇文章将介绍Golang
并发编程中常用到一种编程模式:context
。本文将从为什么需要context
出发,深入了解context
的实现原理,以及了解如何使用context
。
在并发程序中,由于超时、取消操作或者一些异常情况,往往需要进行抢占操作或者中断后续操作。熟悉channel
的朋友应该都见过使用done channel
来处理此类问题。比如以下这个例子:
func main() { messages := make(chan int, 10) done := make(chan bool) defer close(messages) // consumer go func() { ticker := time.NewTicker(1 * time.Second) for _ = range ticker.C { select { case <-done: fmt.Println("child process interrupt...") return default: fmt.Printf("send message: %d\n", <-messages) } } }() // producer for i := 0; i < 10; i++ { messages <- i } time.Sleep(5 * time.Second) close(done) time.Sleep(1 * time.Second) fmt.Println("main process exit!") }
上述例子中定义了一个buffer
为0的channel done
, 子协程运行着定时任务。如果主协程需要在某个时刻发送消息通知子协程中断任务退出,那么就可以让子协程监听这个done channel
,一旦主协程关闭done channel
,那么子协程就可以推出了,这样就实现了主协程通知子协程的需求。这很好,但是这也是有限的。
如果我们可以在简单的通知上附加传递额外的信息来控制取消:为什么取消,或者有一个它必须要完成的最终期限,更或者有多个取消选项,我们需要根据额外的信息来判断选择执行哪个取消选项。
考虑下面这种情况:假如主协程中有多个任务1, 2, …m,主协程对这些任务有超时控制;而其中任务1又有多个子任务1, 2, …n,任务1对这些子任务也有自己的超时控制,那么这些子任务既要感知主协程的取消信号,也需要感知任务1的取消信号。
如果还是使用done channel
的用法,我们需要定义两个done channel
,子任务们需要同时监听这两个done channel
。嗯,这样其实好像也还行哈。但是如果层级更深,如果这些子任务还有子任务,那么使用done channel
的方式将会变得非常繁琐且混乱。
我们需要一种优雅的方案来实现这样一种机制:
这个时候context
就派上用场了。我们首先看看context
的结构设计和实现原理。
先看Context
接口结构,看起来非常简单。
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
Context
接口包含四个方法:
Deadline
返回绑定当前context
的任务被取消的截止时间;如果没有设定期限,将返回ok == false
。Done
当绑定当前context
的任务被取消时,将返回一个关闭的channel
;如果当前context
不会被取消,将返回nil
。Err
如果Done
返回的channel
没有关闭,将返回nil
;如果Done
返回的channel
已经关闭,将返回非空的值表示任务结束的原因。如果是context
被取消,Err
将返回Canceled
;如果是context
超时,Err
将返回DeadlineExceeded
。Value
返回context
存储的键值对中当前key
对应的值,如果没有对应的key
,则返回nil
。可以看到Done
方法返回的channel
正是用来传递结束信号以抢占并中断当前任务;Deadline
方法指示一段时间后当前goroutine
是否会被取消;以及一个Err
方法,来解释goroutine
被取消的原因;而Value
则用于获取特定于当前任务树的额外信息。而context
所包含的额外信息键值对是如何存储的呢?其实可以想象一颗树,树的每个节点可能携带一组键值对,如果当前节点上无法找到key
所对应的值,就会向上去父节点里找,直到根节点,具体后面会说到。
再来看看context
包中的其他关键内容。
emptyCtx
是一个int
类型的变量,但实现了context
的接口。emptyCtx
没有超时时间,不能取消,也不能存储任何额外信息,所以emptyCtx
用来作为context
树的根节点。
// An emptyCtx is never canceled, has no values, and has no deadline. It is not // struct{}, since vars of this type must have distinct addresses. type emptyCtx int这篇关于深入理解Golang之context的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!