首页 > 其他分享 >go语言context.Context

go语言context.Context

时间:2023-04-22 22:55:56浏览次数:28  
标签:Context context return parent err key go

go语言context.Context

context 是 Golang 从 1.7 版本引入的一个标准库。它使得一个request范围内所有goroutine运行时的取消可以得到有效的控制。当最上层的 goroutine 因为某些原因执行失败时,下层的 Goroutine 由于没有接收到这个信号所以会继续工作;但是当我们正确地使用 context.Context 时,就可以在下层及时停掉无用的工作以减少额外资源的消耗。

数据结构

type Context interface {
	// Deadline returns the time when work done on behalf of this context
	// should be canceled. Deadline returns ok==false when no deadline is
	// set. Successive calls to Deadline return the same results.
	Deadline() (deadline time.Time, ok bool)

	// Done returns a channel that's closed when work done on behalf of this
	// context should be canceled. Done may return nil if this context can
	// never be canceled. Successive calls to Done return the same value.
	//
	// WithCancel arranges for Done to be closed when cancel is called;
	// WithDeadline arranges for Done to be closed when the deadline
	// expires; WithTimeout arranges for Done to be closed when the timeout
	// elapses.
	//
	// Done is provided for use in select statements:
	//
	//  // Stream generates values with DoSomething and sends them to out
	//  // until DoSomething returns an error or ctx.Done is closed.
	//  func Stream(ctx context.Context, out chan<- Value) error {
	//  	for {
	//  		v, err := DoSomething(ctx)
	//  		if err != nil {
	//  			return err
	//  		}
	//  		select {
	//  		case <-ctx.Done():
	//  			return ctx.Err()
	//  		case out <- v:
	//  		}
	//  	}
	//  }
	//
	// See https://blog.golang.org/pipelines for more examples of how to use
	// a Done channel for cancellation.
	Done() <-chan struct{}

	// If Done is not yet closed, Err returns nil.
	// If Done is closed, Err returns a non-nil error explaining why:
	// Canceled if the context was canceled
	// or DeadlineExceeded if the context's deadline passed.
	// After Err returns a non-nil error, successive calls to Err return the same error.
	Err() error

	// Value returns the value associated with this context for key, or nil
	// if no value is associated with key. Successive calls to Value with
	// the same key returns the same result.
	//
	// Use context values only for request-scoped data that transits
	// processes and API boundaries, not for passing optional parameters to
	// functions.
	//
	// A key identifies a specific value in a Context. Functions that wish
	// to store values in Context typically allocate a key in a global
	// variable then use that key as the argument to context.WithValue and
	// Context.Value. A key can be any type that supports equality;
	// packages should define keys as an unexported type to avoid
	// collisions.
	//
	// Packages that define a Context key should provide type-safe accessors
	// for the values stored using that key:
	//
	// 	// Package user defines a User type that's stored in Contexts.
	// 	package user
	//
	// 	import "context"
	//
	// 	// User is the type of value stored in the Contexts.
	// 	type User struct {...}
	//
	// 	// key is an unexported type for keys defined in this package.
	// 	// This prevents collisions with keys defined in other packages.
	// 	type key int
	//
	// 	// userKey is the key for user.User values in Contexts. It is
	// 	// unexported; clients use user.NewContext and user.FromContext
	// 	// instead of using this key directly.
	// 	var userKey key
	//
	// 	// NewContext returns a new Context that carries value u.
	// 	func NewContext(ctx context.Context, u *User) context.Context {
	// 		return context.WithValue(ctx, userKey, u)
	// 	}
	//
	// 	// FromContext returns the User value stored in ctx, if any.
	// 	func FromContext(ctx context.Context) (*User, bool) {
	// 		u, ok := ctx.Value(userKey).(*User)
	// 		return u, ok
	// 	}
	Value(key interface{}) interface{}
}

Context是一个interface,只要实现了这四个方法,就可以认为是一个Context

  • Deadline方法返回一个当前context应该被取消的截止时间,如果没设置的话返回false,多次调用返回相同的结果
  • Done方法会返回一个channel,该channel在context被取消的时候会关闭,当channel被关闭时,监听此channel的goroutine会读出0值,这时候就可以停掉自己
  • Err方法会在channel关闭之前返回空,在关闭后会返回关闭的原因
  • Value方法用来返回context中保存的值

实现

emptyCtx

官方库提供的最基础的 Context 实现,emptyCtx是一个int类型的变量,但实现了context的接口。emptyCtx没有超时时间,不能取消,也不能存储任何额外信息,所以emptyCtx用来作为context树的根节点。我们常用的 context.Background() 与 context.TODO() 底层即为 emptyCtx,没有 Deadline,也获取不到 Value。日常使用的绝大部分 Context 都是在 emptyCtx 的基础上派生出来的。

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 interface{}) interface{} {
	return nil
}

func (e *emptyCtx) String() string {
	switch e {
	case background:
		return "context.Background"
	case todo:
		return "context.TODO"
	}
	return "unknown empty Context"
}

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
	return background
}

// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
	return todo
}

valueCtx

调用context.withValue方法可以返回一个valueCtx

// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
func WithValue(parent Context, key, val interface{}) Context {
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

valueCtx 在内嵌 Context 的同时,包含了一组键值对,皆为 interface{},我们经常使用的 context.WithValue() 函数底层就是一个嵌入了参数

// 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 interface{}
}

Value() 获取值时为递归获取,自下而上形成查询链条

func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}

因为递归获取,所以若 child context 的 key 与 parent context相同,会直接覆盖掉。 parent context是感知不到 child context 的行为的。

cancelCtx

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     chan struct{}         // created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
}

用锁来保护内置的成员变量

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

WithCancel方法可以创建一个cancelCtx,并且和它的取消方法

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

newCancelCtx方法可以创建一个cancelCtx,初始化的时候将parent记录在Context中

propagateCancel

// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
	if parent.Done() == nil {
		return // parent is never canceled
	}
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	for {
		switch c := parent.(type) {
		case *cancelCtx:
			return c, true
		case *timerCtx:
			return &c.cancelCtx, true
		case *valueCtx:
			parent = c.Context
		default:
			return nil, false
		}
	}
}

propagateCancel方法首先会判断父context能否被取消,不能的话直接返回,然后向上查找可取消的父context,如果找到了,并且父context的err字段不为空的话,说明父context已经被取消了,这时候直接把child也取消掉,如果父context的err为空的话,就将child加入到父context的孩子列表中,如果没有找到可取消的父context,那么启一个协程监听parent和child的done channel

cancel

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
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
	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)
	}
}

cancel 的作用是关闭 当前上下文以及子上下文的cancelCtx.done channel

首先调用cancel方法的时候需要传入err,如果err为空的话直接panic

  1. 判断context的err是否为空,如果为空的话,说明当前context已经被取消了,直接返回
  2. 给context的err赋值,如果当前context的done channel为空的话,直接赋值一个关闭的channel,否则关闭done channel
  3. 遍历当前context的children列表,递归取消child context
  4. 如果需要从父context中移除自己,那么调用方法移除

标签:Context,context,return,parent,err,key,go
From: https://www.cnblogs.com/zpf253/p/17344367.html

相关文章

  • GoodSync(最好的文件同步软件)
    GoodSync是一款使用创新的最好的文件同步软件,可以在你的台式机、笔记本、USB外置驱动器等设备直接进行数据文件同步软件。GoodSync将高度稳定的可靠性和极其简单的易用性完美结合起来,可以实现两台电脑、电脑与云端网盘、电脑和远程FTP服务器、电脑与U盘之间的数据和文件......
  • golang实现RPC
      一、RPC工作流程:摘自《goweb编程》二、go支持三个级别的RPC(HTTP,TCP,JSONRPC)三、实现http的RPC实例:3.1GORPC的函数只有符合以下条件才能被远程访问函数必须是首字母是大写必须有两个首字母大写的参数第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二......
  • Django框架基础5
    本节知识重点:  1、判断变量值是否相等(equal)  2、extends模板继承标签  3、load加载标签或过滤器  4、模板继承的应用(block与extends)  5、路由分发函数(include)  6、url标签实现反向解析(先app_name='index',再{%url%})  7、reverse()函数实现反向解析(......
  • Discourse Google Analytics 3 的升级提示
    根据Google官方的消息:GoogleAnalytics(分析)4是我们的新一代效果衡量解决方案,即将取代UniversalAnalytics。自2023年7月1日起,标准UniversalAnalytics媒体资源将停止处理新的命中数据。如果您仍在使用UniversalAnalytics,我们建议您为以后使用GoogleAnalytics(分析)4......
  • Django笔记十二之defer、only指定返回字段
    本文首发于公众号:Hunter后端原文链接:Django笔记十二之defer、only指定返回字段本篇笔记将介绍查询中的defer和only两个函数的用法,笔记目录如下:deferonly1、deferdefer的英语单词的意思是延迟、推迟,我们可以通过将字段作为参数传入,可以达到在获取数据的时候指定不获......
  • Django 知识库:path()路径映射
    网站地址是由统一资源定位符表示的,也是就我们常说的url。Django中有非常强大的path()方法,可以动态构造出你想要的各种不同形态的url。基本写法如下:fromdjango.urlsimportpathurlpatterns=[#固定地址path('articles/2003/',...),#可传入int参......
  • Discourse Google Analytics 3 的升级提示
    根据Google官方的消息:GoogleAnalytics(分析)4是我们的新一代效果衡量解决方案,即将取代UniversalAnalytics。自2023年7月1日起,标准UniversalAnalytics媒体资源将停止处理新的命中数据。如果您仍在使用UniversalAnalytics,我们建议您为以后使用GoogleAnalytics(分......
  • Django—Form两种解决表单数据无法动态刷新的方法
    一、无法动态更新数据的实例#Createyourmodelshere.classClasses(models.Model):title=models.CharField(max_length=32)def__str__(self):returnself.titleclassTeacher(models.Model):name=models.CharField(max_length=32)t2c=model......
  • Go中响应式编程库RxGo详细介绍
    最近的项目用到了RxGo,因为之前从没有接触过,特意去学了学,特此记录下。文章很多内容是复制了参考资料或者官方文档。如果涉及侵权,请联系删除,谢谢。1、RxGo简介1.1基础介绍RxGo是一个基于Go语言的响应式编程库,它提供了一种简单而强大的方式来处理异步事件流和数据流。RxGo的......
  • MixGo CE电池驱动板简单介绍
    MixGOCE主控板使用时需要用数据线连接到电脑或者充电宝上,不是很方便,有没有其他可以供电的方法呢?有!!!MixGoCE电池扩展板,将其固定在MixGoCE主控板上,使用5号电池即可供电使用。连接说明CE板四个角有四个电气孔接口,分别是GND、3V3(更新的版本为5V0)、IO17、IO18;电池扩展板上......