Context包源码解析

Context就相当于一个树状结构

最后请回答一下这个问题:context包中的方法是线程安全吗?

Context包中主要有一个接口和三个结构体

Context接口

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

结构体

type valueCtx struct {
Context
key, val interface{}
} 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
} type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. deadline time.Time
}

Context包有两个根实例

package context
...
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)

分别通过以下两个方法返回

其中Background()方法是返回初始化时自动实例化的background对象,TODO方法跟Background()相同

  • context.Background()

  • context.TODO()

func Background() Context {
return background
}
TODO方法跟Background()相同
func TODO() Context {
return todo
}

那么emptyCtx又是什么?

emptyCtx是一个自定义的类型,底层类型为int,实现了Context接口的四个方法,并都返回空值或初始值

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

context包中常用的几个方法

  • 创建具有dealline的Context WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

  • 创建具有取消方法的Context WithCancel(parent Context) (ctx Context, cancel CancelFunc)

  • 创建具有超时的Context WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

  • 创建具有可保存键值的Context WithValue(parent Context, key, val interface{}) Context

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

type valueCtx struct {
Context //相当于父节点
key, val interface{}
} func WithValue(parent Context, key, val interface{}) 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}
}
//重写了Context接口中的 Value(key interface{})方法
func (c *valueCtx) Value(key interface{}) interface{} {
//先从自己节点中的键对值去寻找
if c.key == key {
return c.val
}
//找不到就往上递归,依次寻找绑定的父节点的value
return c.Context.Value(key)
}

写一个小demo验证一下

func main() {
ctx := context.WithValue(context.Background(), "xiaofu", "test")
ctx1 := context.WithValue(ctx, "xiaofu1", "test1") fmt.Println(ctx1.Value("xiaofu1"))
fmt.Println(ctx1.Value("xiaofu")) }
//输出
test1
test
<nil>
//说明是会往上递归,直到找到background的根节点

WithCancel(parent Context) (ctx Context, cancel CancelFunc)

查看以下代码,都会发现每次新建Context,都会绑定父节点的Context

type cancelCtx struct {
Context //父节点 mu sync.Mutex // 锁
done chan struct{} // channel,用于关闭
children map[canceler]struct{} // 用于储存子节点中的cancelCtx
err error // set to non-nil by the first cancel call
} //重写了Value方法,当key为cancelCtxKey时,返回当前的cancelCtx,否则不断向上递归寻找cancelCtx
func (c *cancelCtx) Value(key interface{}) interface{} {
if key == &cancelCtxKey {
return c
}
return c.Context.Value(key)
}
//重写了Done方法
func (c *cancelCtx) Done() <-chan struct{} {
//上锁
c.mu.Lock()
//初始化done的channel,根节点的Done()方法返回的是nil
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
//解锁
c.mu.Unlock()
return d
} func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
} var closedchan = make(chan struct{}) //用于控制取消操作的接口,其中因为cancelCtx实现了cancel方法和Done()方法,所以默认实现该接口
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
} 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) }
} //创建cancelCtx结构体,并绑定父节点
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}

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

type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. deadline time.Time
} 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)
}
//把当前的父节点用一个中间对象cancelCtx做转换,同时绑定到timeCtx中
//约等于 temp := cancelCtx{Context: parent}
// c := &timerCtx{
// cancelCtx: temp,
// deadline: d,
// }
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) }
}

WithTimeout(parent Context, timeout time.Duration)

//相当于在当前时间dealline的基础上,往后延迟一段时间,所以可以调用WithDeadline方法
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}

context包中的方法是线程安全吗?

是线程安全

因为每次执行WithXXX方法,都会新建一个context对象,并且把父对象进行绑定。

见demo

func main() {
ctx := context.WithValue(context.Background(), "xiaofu", "test")
go func() {
_ = context.WithValue(ctx, "xiaofu", "test1")
}()
go func() {
_ = context.WithValue(ctx, "xiaofu", "test2")
}() fmt.Println(ctx.Value("xiaofu"))
fmt.Println(ctx.Value("xiaofu"))
time.Sleep(3 * time.Second)
}
//输出
//test
//test

Context包源码解析(附面经)的更多相关文章

  1. Go语言 context包源码学习

    你必须非常努力,才能看起来毫不费力! 微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 日常 Go 开发中,Context 包是用的最多的一个了,几乎所有函数 ...

  2. golang的flag包源码解析与使用

    当我们 import  package时,package内的全局常量和全局变量会进行初始化,并且紧接着init函数会执行.因此我们先看一下flag包的全局常量和全局变量. 一.flag包的全局常量.全 ...

  3. multiprocessing 源码解析 更新中......

    一.参考链接 1.源码包下载·链接:   https://pypi.org/search/?q=multiprocessing+ 2.源码包 链接:https://pan.baidu.com/s/1j ...

  4. MapReduce中一次reduce方法的调用中key的值不断变化分析及源码解析

    摘要:mapreduce中执行reduce(KEYIN key, Iterable<VALUEIN> values, Context context),调用一次reduce方法,迭代val ...

  5. jdk下httpserver源码解析

    在写这篇博客之前我查了很久发现全网都没有一篇写httpserver源码解析的 所以今天就由我来为大家解析一下httpserver的源码.(这里我会去掉其中的https部分的源码,只讲http部分,对h ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  8. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  9. 源码解析-Volley(转自codeKK)

    Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon ...

随机推荐

  1. Flink源码学习笔记(2) 基于Yarn的自动伸缩容实现

    1.背景介绍 随着实时计算技术在之家内部的逐步推广,Flink 任务数及计算量都在持续增长,集群规模的也在逐步增大,本着降本提效的理念,我们研发了 Flink 任务伸缩容功能: 提供自动伸缩容功能,可 ...

  2. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  3. 2022.02.04 Day1

    前言 为日后的算法面试做准备,自己也很喜欢算法这个东西,每天3~5道题. 刷题 1.leetcode 209 长度最下的子数组 题目 长度最下的子数组 大致的意思是从数组中找到长度最小的数组,并且数组 ...

  4. ApacheCN Java 译文集 20211012 更新

    Effective Java 中文第三版 1. 考虑使用静态工厂方法替代构造方法 2. 当构造方法参数过多时使用 builder 模式 3. 使用私有构造方法或枚类实现 Singleton 属性 4. ...

  5. Dart 2.16 现已发布

    文 / Michael Thomsen, Dart 产品经理 Dart 2.16 正式发布 Dart 2.16 正式版已于上周发布.尽管没有新的语言特性加入,但本次版本发布包含了数个问题修复 (包括对 ...

  6. NOIP2018 Day2T3 保卫王国

    首先不考虑强制要求的话是一个经典问题,令 \(f_{i, 0 / 1}\) 为 \(i\) 选或不选时以 \(i\) 为根的子树的最优答案.那么就有转移 \(f_{u, 0} = \sum f_{v, ...

  7. 云服务器的windows系统上部署项目

    1.购买云服务器 以百度云服务器为例 https://cloud.baidu.com/campaign/midyear2020/index.html?track=cp:npinzhuan|pf:pc| ...

  8. Web容器中DefaultServlet详解

    万分感谢原文作者:_licho 原文链接:https://blog.csdn.net/qq_30920821/article/details/78328608 Web容器中DefaultServlet ...

  9. Mysql Json函数之搜索 (三)

    本节中的函数对JSON值执行搜索操作,以从其中提取数据,报告数据是否在其中的某个位置或报告其中的数据的路径. JSON_CONTAINS(target, candidate[, path]) 通过返回 ...

  10. Linux下Wordpress忘记密码后的解决方法

    进入Wordpress的数据库,找到wp_users表,使用MD5('你的密码')函数添加密码 示例: 修改admin的密码为123456 UPDATE wp_users SET user_pass= ...