这篇“Go中的Context怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go中的Context怎么使用”文章吧。
Context 接口定义如下
type Context interface {
// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that is closed when this Context is canceled
// or times out.
Done() <-chan struct{}
// Err indicates why this context was canceled, after the Done channel
// is closed.
Err() error
// Value returns the value associated with key or nil if none.
Value(key any) any
}
Deadline()
: 返回的第一个值是 截止时间,到了这个时间点,Context 会自动触发 Cancel 动作。返回的第二个值是 一个布尔值,true 表示设置了截止时间,false 表示没有设置截止时间,如果没有设置截止时间,就要手动调用 cancel 函数取消 Context。
Done()
: 返回一个只读的通道(只有在被cancel后才会返回),类型为 struct{}
。当这个通道可读时,意味着parent context已经发起了取消请求,根据这个信号,开发者就可以做一些清理动作,退出goroutine。这里就简称信号通道吧!
Err()
:返回Context 被取消的原因
Value
: 从Context中获取与Key关联的值,如果没有就返回nil
context
包提供了四种方法来创建context对象,具体方法如下:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}
func WithValue(parent Context, key, val any) Context {}
由以上方法可知:新的context对象都是基于父context对象衍生的.
WithCancel
:创建可以取消的Context
WithDeadline
: 创建带有截止时间的Context
WithTimeout
:创建带有超时时间的Context,底层调用的是WithDeadline
方法
WithValue
:创建可以携带KV型数据的Context
简单的树状关系如下(实际可衍生很多中):
context
包默认提供了两个根context 对象background
和todo
;看实现两者都是由emptyCtx创建的,两者的区别主要在语义上,
context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生出来;
context.TODO 应该仅在不确定应该使用哪种上下文时使用
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background 创建background context
func Background() Context {
return background
}
// TODO 创建todo context
func TODO() Context {
return todo
}
具体结构如下,我们大致看下相关结构体中包含的字段,具体字段的含义及作用将在下面分析中会提及。
emptyCtx
type emptyCtx int // 空context
cancelCtx
type cancelCtx struct {
Context // 父context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // cancel的原因
}
timerCtx
type timerCtx struct {
cancelCtx //父context
timer *time.Timer // 定时器
deadline time.Time // 截止时间
}
valueCtx
type valueCtx struct {
Context // 父context
key, val any // kv键值对
}
emptyCtx
实现非常简单,具体代码如下,我们简单看看就可以了
// 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
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key any) any {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
cancelCtx
的实现相对复杂点,比如下面要介绍的timeCtx 底层也依赖它,所以弄懂cancelCtx
的工作原理就能很好的理解context
.
cancelCtx
不仅实现了Context
接口也实现了canceler
接口
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil { // 参数校验
panic("cannot create context from nil parent")
}
// cancelCtx 初始化
c := newCancelCtx(parent)
propagateCancel(parent, &c) // cancelCtx 父子关系维护及传播取消信号
return &c, func() { c.cancel(true, Canceled) } // 返回cancelCtx对象及cannel方法
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
用户调用WithCancel
方法,传入一个父 Context(这通常是一个 background
,作为根节点),返回新建的 context,并通过闭包的形式返回了一个 cancel 方法。如果想要取消context时需手动调用cancel方法。
cancelCtx对象初始化, 其结构如下:
type cancelCtx struct {
// 父 context
Context // parent context
// 锁 并发场景下保护cancelCtx结构中字段属性的设置
mu sync.Mutex // protects following fields
// done里存储的是信号通道,其创建方式采用的是懒加载的方式
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
// 记录与父子cancelCtx对象,
children map[canceler]struct{} // set to nil by the first cancel call
// 记录ctx被取消的原因
err error // set to non-nil by the first cancel call
}
propagateCancel
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
done := parent.Done() // 获取parent ctx的信号通道 done
if done == nil { // nil 代表 parent ctx 不是canelctx 类型,不会被取消,直接返回
return // parent is never canceled
}
select { // parent ctx 是cancelCtx类型,判断其是否被取消
case <-done:
// parent is already canceled
child.cancel(false, parent.Err())
return
default:
}
//parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的
if p, ok := parentCancelCtx(parent); ok { // 查询到
p.mu.Lock() // 加锁
if p.err != nil { // 祖父 ctx 已经被取消了,则 子cancelCtx 也需要调用cancel 方法来取消
// parent has already been canceled
child.cancel(false, p.err)
} else { // 使用map结构来维护 将child加入到祖父context中
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()// 解锁
} else { // 开启协程监听 parent Ctx的取消信号 来通知child ctx 取消
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// parentCancelCtx returns the underlying *cancelCtx for parent.
// It does this by looking up parent.Value(&cancelCtxKey) to find
// the innermost enclosing *cancelCtx and then checking whether
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
// parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
// closedchan 代表此时cancelCtx 已取消, nil 代表 ctx不是cancelCtx 类型的且不会被取消
if done == closedchan || done == nil {
return nil, false
}
// 向上遍历查询canelCtx 类型的ctx
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok { // 没有
return nil, false
}
// 存在判断信号通道是不是相同
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
return nil, false
}
return p, true
}
cancelCtx
也实现了canceler
接口,实现可以 取消上下文的功能。
canceler
接口定义如下:
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error) // 取消
Done() <-chan struct{} // 只读通道,简称取消信号通道
}
cancelCtx
接口实现如下:
整体逻辑不复杂,逻辑简化如下:
当前 cancelCtx 取消 且 与之其关联的子 cancelCtx 也取消
根据removeFromParent标识来判断是否将子 cancelCtx 移除
注意
由于信号通道的初始化采用的懒加载方式,所以有未初始化的情况;
已初始化的:调用close 函数关闭channel
未初始化的:用 closedchan
初始化,其closedchan
是已经关闭的channel。
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
closedchan
可重用的关闭通道,该channel通道默认已关闭
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
close(closedchan) // 调用close 方法关闭
}
cancelCtx
源码已经分析完毕,那timerCtx
理解起来就很容易。
关注点:timerCtx
是如何取消上下文的,以及取消上下文的方式
WithTimeout
底层调用是WithDeadline 方法 ,截止时间是 now+timeout;
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
WithDeadline
整体逻辑并不复杂,从源码中可分析出timerCtx
取消上下文 采用两种方式 自动和手动;其中自动方式采用定时器去处理,到达触发时刻,自动调用cancel方法。
deadline
: 截止时间
timer *time.Timer
: 定时器
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
调用cancelCtx的cancel 方法
根据removeFromParent标识,为true 调用removeChild 方法 从它的父cancelCtx的children中移除
关闭定时器 ,防止内存泄漏(着重点)
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
valueCtx
结构体中有key
和val
两个字段,WithValue
方法也是将数据存放在该字段上
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val any
}
func (c *valueCtx) Value(key any) any {
if c.key == key { // 判断当前valuectx对象中的key是否匹配
return c.val
}
return value(c.Context, key)
}
// value() 向根部方向遍历,直到找到与key对应的值
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey { // 获取cancelCtx对象
return c
}
c = ctx.Context
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case *emptyCtx:
return nil
default:
return c.Value(key)
}
}
}
以上就是关于“Go中的Context怎么使用”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://juejin.cn/post/7222128282921615397