完整教程:Go语言的context

news/2025/10/6 9:15:56/文章来源:https://www.cnblogs.com/tlnshuju/p/19127281

完整教程:Go语言的context

2025-10-06 09:10  tlnshuju  阅读(0)  评论(0)    收藏  举报

Golang context 实现原理

本篇文章是基于小徐先生的文章的修改和个人注解,要查看原文可以点击上述的链接查看

目前我这篇文章的go语言版本是1.24.1

context上下文

context被当作第一个参数(官方建议),并且不断的传递下去,基本上一个项目代码到处都是context,但是你们真的知道他有什么作用吗?

接下来就来看看这个golang世界中的典型工具吧

func main()  {    ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second)    defer cancel()    go Monitor(ctx)     time.Sleep(20 * time.Second)} func Monitor(ctx context.Context)  {    for {        fmt.Print("monitor")    }}

但是他到底时如何处理并发控制和实现呢?

接下来就来深入看看他的原理和使用

一.context包介绍

context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型,可以使用backgroundTODO创建一个上下文,在函数调用链之间传播context,也可以使用WithDeadlineWithTimeoutWithCancelWithValue 创建的修改副本替换它,听起来有点绕,其实总结起就是一句话:context的作用就是在不同的goroutine之间同步请求特定的数据、取消信号以及处理请求的截止日期。

目前我们常用的一些库都是支持context的,例如gindatabase/sql等库都是支持context的,这样更方便我们做并发控制了,只要在服务器入口创建一个context上下文,不断透传下去即可。

二.context的使用

2.1 context.Context

他是核心数据结构,看一下它的样子吧:

type Context interface {    Deadline() (deadline time.Time, ok bool)    Done() <-chan struct{}    Err() error    Value(key any) any}

Context 为 interface,定义了四个核心 api:

• Deadline:返回 context 的过期时间

• Done:返回 context 中的 channel

• Err:返回错误

• Value:返回 context 中的对应 key 的值

2.2 标准error

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string   { return "context deadline exceeded" }func (deadlineExceededError) Timeout() bool   { return true }func (deadlineExceededError) Temporary() bool { return true }

• Canceled:context 被 cancel 时会报此错误

• DeadlineExceeded:context 超时时会报此错误

三.类的实现

3.1 emptyCtx

通过看它的源码我们会发现,这个空的上下文其实就是实现了这个context这个接口,对于实现的4个方法都是返回的nil,这就是为什么说他是empty

在之前的版本中他可能是int类型,后来已经被修改了

type emptyCtx struct{} 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}

• emptyCtx 是一个空的 context

• Deadline 方法会返回一个公元元年时间以及 false 的 flag,标识当前 context 不存在过期时间;

• Done 方法返回一个 nil 值,用户无论往 nil 中写入或者读取数据,均会陷入阻塞;

• Err 方法返回的错误永远为 nil;

• Value 方法返回的 value 同样永远为 nil.

3.1.1 context.Background() & context.TODO()

context包主要提供了两种方式创建context:

  • context.Backgroud()
  • context.TODO()

我们在看代码的时候,经常可以看到不是Background就是todo作为上下文的起始,那他们有什么区别呢?

两者的区别

这两个函数其实只是互为别名,没有差别,官方给的定义是:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。
  • context.TODO 应该只在不确定应该使用哪种上下文时使用;

所以在大多数情况下,我们都使用context.Background作为起始的上下文向下传递。

看一下两者的底层是什么?

两者其实都是一个对context的一个继承

type backgroundCtx struct{ emptyCtx }type todoCtx struct{ emptyCtx } func Background() Context {	return backgroundCtx{}} func TODO() Context {	return todoCtx{}}

看到这里,你会发现上面的两种方式是创建根context,不具备任何功能,具体实践其实还是要依靠context包提供的With系列函数来进行派生:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Context

这四个函数都要基于父Context衍生,通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,画个图表示一下:

基于一个父Context可以随意衍生,其实这就是一个Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,每个子节点都依赖于其父节点,例如上图,我们可以基于Context.Background衍生出四个子contextctx1.0-cancelctx2.0-deadlinectx3.0-timeoutctx4.0-withvalue,这四个子context还可以作为父context继续向下衍生,即使其中ctx1.0-cancel 节点取消了,也不影响其他三个父节点分支。

创建context方法和context的衍生方法就这些,关于这些with函数,会在后续的使用中看到

3.1.2 With类函数

  1. WithCancel(parent Context)

用途:创建一个新的上下文和取消函数。当调用取消函数时,所有派生自这个上下文的操作将被通知取消。

应用场景:当一个长时间运行的操作需要能够被取消时。例如,用户在网页中点击“取消”按钮时,相关的数据库或 HTTP 请求应立即停止。

2. WithDeadline(parent Context, d time.Time)

用途:创建一个新的上下文,该上下文在指定的时间点自动取消。

应用场景:在请求处理时设置最大执行时间。例如,调用外部 API 时,如果响应时间超过预期,将自动取消请求,以避免无效的等待。

3. WithTimeout(parent Context, timeout time.Duration)

用途:创建一个新的上下文,它会在指定的持续时间内自动取消。

应用场景:适用于设置操作的超时时间,确保系统不会在某个操作上无休止地等待。常用于网络请求或长时间运行的任务。

4. WithValue(parent Context, key, val interface{})

用途:创建一个新的上下文,并将键值对存储在该上下文中。

应用场景:在处理请求时,将特定的数据(如用户身份信息、RequestID)在处理链中传递,而不需要在每个函数参数中显式传递。

3.2 cancelCtx

接下来看一下第二个实现的类吧,看名字就能看出来他是一个带有取消功能的上下文。

type cancelCtx struct {	Context 	mu       sync.Mutex            	done     atomic.Value          	children map[canceler]struct{} // 这里是一个set{}	err      error                 	cause    error                } type canceler interface {	cancel(removeFromParent bool, err, cause error)	Done() <-chan struct{}} // 这里体现了goland的一个编程哲学// 作为一个父context,它只需要关注子类的这两个方法即可,// 它的子类可能更有能力,但是与父亲无关,只需要知道他是否还存在即可// 会通过就近生成interface的方式,把无关的信息都屏蔽掉。// 也就是谁使用谁声明谁管理

• 继承了一个 context 作为其父 context. 可见,cancelCtx 必然为某个 context 的子 context

• 内置了一把锁,用以协调并发场景下的资源获取;

• done:实际类型为 chan struct{},即用以反映 cancelCtx 生命周期的通道;

• children:一个 set,指向 cancelCtx 的所有子 context;

• err:记录了当前 cancelCtx 的错误. 必然为某个 context 的子 context;

• cause:是在go1.20之后加入的字段,主要作用适用于记录导致context被取消的具体原因

在这里,要加入一些其他的内容----同步调用和异步调用

同步调用,其实就是一种串行的方式,也就是我们平时写的程序,他是一步一步进行下去,类似一条链的形式。

而异步调用则是开辟协程,并且不会阻塞主线程,并且主线程对子协程的感知能力很弱,开辟了多个子协程,就会形成类似树的形式

就会导致,主线程对子协程的管理能力下降,从而致使协程无法回收,最后导致协程泄露的一个问题。

在创建协程方面,我们要知道一点,如果不知道你创建的协程什么时候结束,你就不应该去创建,不应该滥用并发。

如何解决这个协程的控制呢?

那么今天的主角就是cancelCtx了

先说cancelCtx继承了Context,对其方法进行了一个重写,但是并没有对Deadline方法重写,而是直接继承的父类的。Deadline不进行重写是因为他没有过期取消的能力。

func (c *cancelCtx) Value(key any) any {    // 这里为什么要加入这个判断,在后续会介绍,主要就是用于判断    // 其自身是否是一个cancelCtx类型,这个cancelCtxKey是一个定值	if key == &cancelCtxKey {		return c	}	return value(c.Context, key)} func (c *cancelCtx) Done() <-chan struct{} {	d := c.done.Load()	if d != nil {		return d.(chan struct{})	}	c.mu.Lock()	defer c.mu.Unlock()	d = c.done.Load()	if d == nil {		d = make(chan struct{})		c.done.Store(d)	}	return d.(chan struct{})} func (c *cancelCtx) Err() error {	c.mu.Lock()	err := c.err	c.mu.Unlock()	return err}

3.2.1 WithCancel取消控制

既然想实现这种父子联动的行为,就轮到了With下的一个函数WithCancel函数,通过这个函数从而得到一个cancelCtx对象,从而实现一个对子协程的一个控制

func main()  {    ctx,cancel := context.WithCancel(context.Background())    // 以context.Background()为父,创建得到一个子context和cancel    go Speak(ctx)    time.Sleep(10*time.Second)    cancel()    time.Sleep(1*time.Second)} func Speak(ctx context.Context)  {    for range time.Tick(time.Second){        select {        case <- ctx.Done():            fmt.Println("我要闭嘴了")            return        default:            fmt.Println("balabalabalabala")        }    }}

来对这个例子做出一个解释:

select就相当于是一个多路复用,进行一个监听的操作,通过这个WithCancel获取上下文和取消函数

当调用这个cancel函数的时候,就会直接通过这个ctx.Done发送这个取消机制,从而实现一个控制的效果

这里的操作也就是我们常说的超时控制了,当然cancel并没有涉及到超时,他是通过调用cancel()才可以实现一个关闭。

这个结构体就是cancel取消函数的结构体,他返回的是一个函数类型,所以调用的时候需要加上()

来看下这个函数的具体流程这里先提前告知一点,如果停止一个cancelCtx,则这个它下面所有的子上下文都将被杀死,具体的操作看propagateCancel函数

type CancelFunc func() func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {	c := withCancel(parent)    //Canceled是一个error,自定义的错误信息	return c, func() { c.cancel(true, Canceled, nil) }} func withCancel(parent Context) *cancelCtx {	if parent == nil {		panic("cannot create context from nil parent")	}	c := &cancelCtx{}	c.propagateCancel(parent, c)	return c} func (c *cancelCtx) propagateCancel(parent Context, child canceler) {	c.Context = parent    // 这一步就是说父亲是emptyCtx,他就没有必要取消    // 所以没有必要大费周折的取消它,直接就返回就行	done := parent.Done()	if done == nil {		return // parent is never canceled	}        // 如果父亲被取消了,那儿子也应该直接取消,记录一下取消的错误和原因	select {	case <-done:		// parent is already canceled		child.cancel(false, parent.Err(), Cause(parent))		return	default:	}     // 如果我的父也是cancelCtx,则我需要将孩子加入map里面	if p, ok := parentCancelCtx(parent); ok {		// parent is a *cancelCtx, or derives from one.		p.mu.Lock()		if p.err != nil {			// parent has already been canceled			child.cancel(false, p.err, p.cause)		} else {			if p.children == nil {				p.children = make(map[canceler]struct{})			}			p.children[child] = struct{}{}		}		p.mu.Unlock()		return	}     //检查父Context是否支持AfterFunc,也就是一个回调机制    // 	if a, ok := parent.(afterFuncer); ok {		// parent implements an AfterFunc method.		c.mu.Lock()		stop := a.AfterFunc(func() {			child.cancel(false, parent.Err(), Cause(parent))		})		c.Context = stopCtx{			Context: parent,			stop:    stop,		}		c.mu.Unlock()		return	}     // 开启一个守护协程,时刻监听,第一个判断父亲是不是被终止了    // 如果父亲被终止了,就应该给孩子也砍一刀,让他们也都终止    // 如果孩子被终止了,那就被终止了,什么也不需要处理,传播具有单向性	goroutines.Add(1)	go func() {		select {		case <-parent.Done():			child.cancel(false, parent.Err(), Cause(parent))		case <-child.Done():		}	}()}

parentCancelCtx用于判断是不是cancelCtx类型

func (c *cancelCtx) Value(key any) any {    // 这里为什么要加入这个判断,在后续会介绍,主要就是用于判断    // 其自身是否是一个cancelCtx类型,这个cancelCtxKey是一个定值	if key == &cancelCtxKey {		return c	}	return value(c.Context, key)} func parentCancelCtx(parent Context) (*cancelCtx, bool) {	done := parent.Done()	if done == closedchan || done == nil {		return nil, false	}	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.cancel 方法有三个入参,第一个 removeFromParent 是一个 bool 值,表示当前 context 是否需要从父 context 的 children set 中删除;第二个 err 则是 cancel 后需要展示的错误,第三个则表示导致错误原因。

func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {	if err == nil {		panic("context: internal error: missing cancel error")	}	if cause == nil {		cause = err	}	c.mu.Lock()	if c.err != nil {		c.mu.Unlock()		return // already canceled	}	c.err = err	c.cause = cause	d, _ := c.done.Load().(chan struct{})	if d == nil {        // closedchan 这里是一个全局chan        // 关闭,从而取消监听,Store就是为了确保原子性和可见性		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, cause)	}	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) {	if s, ok := parent.(stopCtx); ok {		s.stop()		return	}	p, ok := parentCancelCtx(parent)	if !ok {		return	}	p.mu.Lock()	if p.children != nil {		delete(p.children, child)	}	p.mu.Unlock()}

3.3 timerCtx

接下来看第三个实现的类

type timerCtx struct {    cancelCtx    timer *time.Timer // Under cancelCtx.mu.    deadline time.Time}

timerCtx 在 cancelCtx 基础上又做了一层封装,除了继承 cancelCtx 的能力之外,新增了一个 time.Timer 用于定时终止 context;另外新增了一个 deadline 字段用于字段 timerCtx 的过期时间.

这样就有了实现时停的操作,它对Dealine进行了一个重写,其他都是继承的cancelCtx的

Deadline返回的是 deadline time.Time

3.3.1WithTimeout和WithDeadline

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {	return WithDeadline(parent, time.Now().Add(timeout))} func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {	return WithDeadlineCause(parent, d, nil)}

这两个函数他们的参数就可以看出区别,context.WithTimeout是指经过的时间段,而WithDeadline则是指时间点。

这里属于是超时取消。

3.4 valueCtx

type valueCtx struct {    Context    key, val any}

说一下这个valueCtx吧,在不同位置设置的value其实在查找方面是有问题的,比如你在最下面那一层去存放,也就是B下面的子节点存放value,只有A和B才可以访问这个value,C和D是无法访问到的。

3.4.1WithValue

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}}

通过这个函数来设置value值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/929142.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国外做农产品有名的网站手机端网站设计模板

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

创意网站建设排行榜wordpress删除摘要

facenet是一款非常经典的神经网络模型&#xff0c;它可以直接学习从人脸图像到欧几里德空间的映射(直接将人脸映射到欧几里得空间)。在欧几里德空间中&#xff0c;距离直接对应于人脸相似性的度量。一旦这个空间产生&#xff0c;使用标准技术&#xff0c;将FaceNet嵌入作为特征…

Python包管理器 uv替代conda? - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

广州建站网络推广公司大气绿色网站模板

引言 随着软件复杂度的不断增加&#xff0c;如何有效地管理类内部的数据变得愈发重要。属性装饰器作为一种强大的工具&#xff0c;不仅简化了代码&#xff0c;还增强了程序的可读性和可维护性。通过使用属性装饰器&#xff0c;我们可以轻松地实现对类属性的读取、修改以及删除…

网站开发维护报价单wordpress的源代码

一.标识符1.标识符的作用&#xff1a;C 标识符是用来标识变量、函数&#xff0c;或任何其他用户自定义项目的名称2.标识符的规范&#xff1a;一个标识符只能以字母 A-Z 或 a-z 或下划线 _ 开始 后跟零个或多个字母、下划线和数字(0-9)&#xff0c;第二位开始也只能用 A-Z…

P2724 [IOI 1998 / USACO3.1] 联系 Contact 做题笔记

前面思考了好久都没想出什么,看了题解才会,我真是太菜了 思路 本题可以暴力枚举解决,但是直接暴力枚举又会超时 怎么办呢,注意到这个序列中只有 \(0\) 和 \(1\),长得像二进制。直接把二进制强压成十进制就不用一位…

教育网站平面设计53建筑人才网

目录 问题描述: 解决方法: 重要代码&#xff1a; 问题描述: 项目中oracle数据库需要转换为mysql&#xff0c;Oracle中的表字段定义为number(36,16)类型的工具自动转换为mysql的decimal(36,16)。在Oracle数据库中&#xff0c;number(36,16)类型的字段&#xff0c;使用BigDeci…

如果能重来

如果能重来如果能重来 好多事,年轻的时候不去尝试,年纪大了就更不可能去尝试,尤其是一个人。年少多去经历,是一笔财富,更是“不负少年时”。2025.10.6

深入解析:DeepSeek 赋能智能零售,解锁动态定价新范式

深入解析:DeepSeek 赋能智能零售,解锁动态定价新范式2025-10-06 08:57 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; d…

家如何网站刷网站seo排名软件

&#xff08;1&#xff09;什么是多态&#xff1f; 同类型的对象&#xff0c;表现出不同的形态。前者指父类&#xff0c;后者指不同的子类 说简单点&#xff0c;就是父类的同一种方法&#xff0c;可以在不同子类中表现出不同的状态&#xff0c;或者说在不同子类中可以实现不同…

实用指南:pyecharts 画一下股票的月K图(输出html)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深入解析:Linux运维笔记:服务器感染 netools 病毒案例

深入解析:Linux运维笔记:服务器感染 netools 病毒案例pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

端午节网站建设目的新余+网站建设

1 开始 这是一个总图 下边慢慢看 我们最基础的写的方式就是xml的方式去写 像这样&#xff0c; 而我们会通过applicationContext的方式去获得我们的bean &#xff0c;我其中一篇博客就写到了applicationContext他的父类就是beanFactory 但是中间的是怎么样处理的呢&#xff1f…

基本型企业网站论文媒体网络推广价格优惠

文章目录 1. 业务背景 1. 业务背景 场景一&#xff1a; 快速响应用户请求 场景描述&#xff1a;比如说⽤户要查看⼀个商品的信息&#xff0c;那么我们需要将商品维度的⼀系列信息如商品的价格、优惠、库存、图⽚等等聚合起来&#xff0c;展示给⽤户。 分析&#xff1a;从用户角…

网站开发的技术解决方案企业网站适合响应式嘛

SonarWiz 8.0.1是功能强大的测绘软件&#xff01;提供强大的数据采集、后处理等功能操作&#xff0c;您将获得灵活完整的报告&#xff0c;并提供丰富的选项以便进行定制和更灵活的进行操作&#xff0c;软件功能齐全&#xff0c;包括完整的海底测绘解决方案&#xff0c;方便实时…

长安网站建设软件摄影网站导航

96.不同的二叉搜索树 力扣题目链接(opens new window) 给定一个整数 n&#xff0c;求以 1 ... n 为节点组成的二叉搜索树有多少种&#xff1f; dp[3] dp[2] * dp[0] dp[1] * dp[1] dp[0] * dp[2] dp[i] &#xff1a; 1到i为节点组成的二叉搜索树的个数为dp[i]。 dp[i] d…

好的高端企业网站建设公司安徽六安特产

下载一些 jar 包驱动&#xff0c;不需用去官网下了&#xff0c;直接去 Maven 中央仓库&#xff0c;高效、简单 Maven 中央仓库地址 https://mvnrepository.com/open-source 我们下期见&#xff0c;拜拜&#xff01;

Linux--进程概念 - 详解

Linux--进程概念 - 详解2025-10-06 08:38 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fon…

设计模式——命令设计模式(行为型) - 详解

设计模式——命令设计模式(行为型) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &quo…

设计模式——访问者设计模式(行为型) - 实践

设计模式——访问者设计模式(行为型) - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &q…