当前位置: 首页 > news >正文

Golang的context

目录

context的基本使用

为什么需要context

Context interface 

 标准 error

emptyCtx 

cancelCtx 

Deadline 方法

Done 方法

Err 方法

Value 方法

context.WithCancel()

newCancelCtx

WithCancel中propagateCancel

cancel

timerCtx

valueCtx  


context的基本使用

创建:

context.Background():创建一个空的父类context

context.TODO(): 创建一个未来 可能有内容的父类 context,有扩展性

	// 创建一个可以取消的ContextctxCancel, cancelCancel := context.WithCancel(context.Background())defer cancelCancel() // 当不再需要时,确保调用cancel来释放资源// 创建一个带有截止时间的Contextdeadline := time.Now().Add(time.Second * 10)ctxDeadline, cancelDeadline := context.WithDeadline(context.Background(), deadline)defer cancelDeadline()// 创建一个带有超时时间的Contexttimeout := 10 * time.SecondctxTimeout, cancelTimeout := context.WithTimeout(context.Background(), timeout)defer cancelTimeout()

返回值: Context 上下文本体 和 CancelFunc 关闭Context 的函数

类别:

context.WithCancel 创建一个可以取消的Context,当调用 返回值 cancel 时 就可以通知关闭

context.WithDeadline 创建一个带有截止时间的Context,到一个特定时间时便发出通知

context.WithTimeout 创建一个带有超时时间的Context,过了一段时间后就发出通知

防止go协程泄漏使用例子

package mainimport ("context""fmt"
)func main() {//WithCancel(ctx Context, cancel CancelFunc)=(名 Context,处理函数 CancelFunc)ctx, cancel := context.WithCancel(context.Background()) //context.Background() 处理 Goroutinecontext.TODO()ch := func(ctx context.Context) <-chan int {ch := make(chan int)go func() {for i := 0; ; i++ {select {case <-ctx.Done():returncase ch <- i:}}}()return ch}(ctx)for v := range ch {fmt.Println(v)if v == 5 {cancel()break}}
}

为什么需要context

场景1: 当主协程启动了m个子协程,m个子协程又启动更多的协程,

那监控起来需要很多的channel, 操作非常繁琐。

如果我们使用 context 时,当父类的context关闭,子类也会一起关闭以此类推,类似Qt中的对象树

场景2:任务A 挂在 任务B 下,我们希望 B 有着定时退出的功能,而且当B退出时A也需要退出

使用定时器+channel时,就显的有些繁琐,我们可以直接使用context.WithTimeout 一步到位

Context interface 

golang的接口 类似 c++的基类

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

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

• Deadline:返回 context 的过期时间;

• Done:返回 context 中的 channel;

• Err:返回错误;

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

 标准 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 超时时会报此错误.

emptyCtx 

重写了接口 api:

type emptyCtx intfunc (*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 
}

返回的什么东西都为空 

• emptyCtx 是一个空的 context,本质上类型为一个整型;

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

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

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

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

var (background = new(emptyCtx)todo       = new(emptyCtx)
)func Background() Context {return background
}func TODO() Context {return todo
}

 当使用context.Background() & context.TODO() 创建emptyCtx 时返回固定的 emptyCtx 

cancelCtx 

type cancelCtx struct {Contextmu       sync.Mutex            // protects following fieldsdone     atomic.Value          // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel call
}type canceler interface {cancel(removeFromParent bool, err error)Done() <-chan struct{}
}

Deadline 方法

cancelCtx 未实现该方法,仅是 embed 了一个带有 Deadline 方法的 Context interface,因此倘若直接调用会报错.


Done 方法

流程如下:

源码:

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


• 基于 atomic 包,读取 cancelCtx 中的 chan;倘若已存在,则直接返回;

• 加锁后,在此检查 chan 是否存在,若存在则返回;(双重检查 double check)

• 初始化 chan 存储到 aotmic.Value 当中,并返回.(懒加载机制 懒汉)

 

Err 方法

func (c *cancelCtx) Err() error {c.mu.Lock()err := c.errc.mu.Unlock()return err
}

• 加锁;

• 读取 cancelCtx.err;

• 解锁;

• 返回结果;

Value 方法

func (c *cancelCtx) Value(key any) any {if key == &cancelCtxKey {return c}return value(c.Context, key)
}

• 倘若 key 特定值 &cancelCtxKey,则返回 cancelCtx 自身的指针;

• 否则遵循 valueCtx 的思路取值返回;

context.WithCancel()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {if parent == nil {panic("cannot create context from nil parent")}c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }
}

• 校验父 context 非空;

• 注入父 context 构造好一个新的 cancelCtx;

• 在 propagateCancel 方法内启动一个守护协程,以保证父 context 终止时,该 cancelCtx 也会被终止;

• 将 cancelCtx 返回,连带返回一个用以终止该 cancelCtx 的闭包函数.

newCancelCtx

没用propagateCancel 方法

func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}

• 注入父 context 后,返回一个新的 cancelCtx.

WithCancel中propagateCancel

propagateCancel 传播取消

func propagateCancel(parent Context, child canceler) {done := parent.Done()if done == nil {return // parent is never canceled}select {case <-done:// parent is already canceledchild.cancel(false, parent.Err())returndefault:}if p, ok := parentCancelCtx(parent); ok {p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()} else {atomic.AddInt32(&goroutines, +1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err())case <-child.Done():}}()}
}

cancel

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
// cancel sets c.cause to cause if this is the first time c is canceled.
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 = errc.cause = caused, _ := 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, cause)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}

timerCtx

只介绍类,其他与cancel差不多,加上了过期时刻


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

valueCtx  

键值对 Ctx

type valueCtx struct {Contextkey, val any
}

参考 : 小徐先生1212 --context 底层实现 

http://www.lryc.cn/news/370505.html

相关文章:

  • Android 各个版本名称和特性总结(持续更新)
  • 9.0 Android中的网络技术
  • linux查看端口是否被占用 / 包含某个字符的文件/当前正在运行的进程/根据端口号查找进程
  • 解锁 JavaScript ES6:函数与对象的高级扩展功能
  • 算法金 | 10 大必知的自动化机器学习库(Python)
  • 微信小游戏开发难度大吗?开发流程有哪些?
  • Qt程序打包成单个exe文件
  • 【机器学习】GANs网络在图像和视频技术中的应用前景
  • MFC 使用sapi文字转换为语音
  • (Git)多人协作1
  • MySQL-分组函数
  • 【C语言】联合(共用体)
  • 【博客715】如何从victorimametrics集群中下线vmstorage节点
  • Redis缓存技术详解与实战
  • 业务架构的位置及关系
  • CMS与AI的融合:构建万能表单小程序系统
  • 机器学习常见知识点 2:决策树
  • 海洋CMS admin_notify.php 远程代码执行漏洞复现(CVE-2024-30565)
  • Spring、Spring MVC、MyBatis和Spring Boot对比
  • 【Linux高级IO】select、poll、epoll
  • Etcd Raft架构设计和源码剖析2:数据流
  • 深入理解Qt多线程编程(QThreadPool)
  • Prisma数据库ORM框架学习
  • Flutter-使用MethodChannel 实现与iOS交互
  • 【星海随笔】云解决方案学习日志篇(一) ELK,kibana,Logstash安装
  • 【leetcode】hot100 哈希表
  • 每日5题Day22 - LeetCode 106 - 110
  • 【Python】读取文件夹中所有excel文件拼接成一个excel表格 的方法
  • 7. 通配符和正则表达式
  • ROS2底层机制源码分析