广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Go语言上下文context底层原理
  • 629
分享到

Go语言上下文context底层原理

2024-04-02 19:04:59 629人浏览 安东尼
摘要

目录1. context 介绍2. 基本介绍3. 源码分析3.1 Context 接口3.2 emptyCtx3.3 cancelCtx3.4 timerCtx3.5 valueCt

1. context 介绍

很多时候,我们会遇到这样的情况,上层与下层的Goroutine需要同时取消,这样就涉及到了goroutine间的通信。在Go中,推荐我们以通信的方式共享内存,而不是以共享内存的方式通信。所以,就需要用到channl,但是,在上述场景中,如果需要自己去处理channl的业务逻辑,就会有很多费时费力的重复工作,因此,context出现了。

context是Go中用来进程通信的一种方式,其底层是借助channlsnyc.Mutex实现的。

2. 基本介绍

context的底层设计,我们可以概括为1个接口,4种实现与6个方法。

1 个接口

  • Context 规定了context的四个基本方法

4 种实现

  • emptyCtx 实现了一个空的context,可以用作根节点
  • cancelCtx 实现一个带cancel功能的context,可以主动取消
  • timerCtx 实现一个通过定时器timer和截止时间deadline定时取消的context
  • valueCtx 实现一个可以通过 key、val 两个字段来存数据的context

6 个方法:

  • Background 返回一个emptyCtx作为根节点
  • TODO 返回一个emptyCtx作为未知节点
  • WithCancel 返回一个cancelCtx
  • WithDeadline 返回一个timerCtx
  • WithTimeout 返回一个timerCtx
  • WithValue 返回一个valueCtx

3. 源码分析

3.1 Context 接口

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline() :返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间
  • Done():返回一个只读chan,如果可以从该 chan 中读取到数据,则说明 ctx 被取消了
  • Err():返回 Context 被取消的原因
  • Value(key):返回key对应的value,是协程安全

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

emptyCtx实现了空的Context接口,其主要作用是为BackgroundTODO这两个方法都会返回预先初始化好的私有变量 background 和 todo,它们会在同一个 Go 程序中被复用:

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

func Background() Context {
    return background
}
func TODO() Context {
	return todo
}

BackgroundTODO在实现上没有区别,只是在使用语义上有所差异:

  • Background是上下文的根节点;
  • TODO应该仅在不确定应该使用哪种上下文时使用;

3.3 cancelCtx

cancelCtx实现了canceler接口与Context接口:

type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

其结构体如下:

type cancelCtx struct {
    // 直接嵌入了一个 Context,那么可以把 cancelCtx 看做是一个 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                 // set to non-nil by the first cancel call
}

我们可以使用WithCancel的方法来创建一个cancelCtx:

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) }
}
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

上面的方法,我们传入一个父 Context(这通常是一个 background,作为根节点),返回新建的 context,并通过闭包的形式,返回了一个 cancel 方法。

newCancelCtx将传入的上下文包装成私有结构体context.cancelCtx

propagateCancel则会构建父子上下文之间的关联,形成树结构,当父上下文被取消时,子上下文也会被取消:

func propagateCancel(parent Context, child canceler) {
    // 1.如果 parent ctx 是不可取消的 ctx,则直接返回 不进行关联
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}
    // 2.接着判断一下 父ctx 是否已经被取消
	select {
	case <-done:
        // 2.1 如果 父ctx 已经被取消了,那就没必要关联了
        // 然后这里也要顺便把子ctx给取消了,因为父ctx取消了 子ctx就应该被取消
        // 这里是因为还没有关联上,所以需要手动触发取消
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}
    // 3. 从父 ctx 中提取出 cancelCtx 并将子ctx加入到父ctx 的 children 里面
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
        // double check 一下,确认父 ctx 是否被取消
		if p.err != nil {
            // 取消了就直接把当前这个子ctx给取消了
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
            // 否则就添加到 children 里面
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
        // 如果没有找到可取消的父 context。新启动一个协程监控父节点或子节点取消信号
		atomic.AddInt32(&goroutines, +1)
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

上面的方法可能遇到以下几种情况:

  • 当 parent.Done() == nil,也就是 parent 不会触发取消事件时,当前函数会直接返回;
  • 当 child 的继承链包含可以取消的上下文时,会判断 parent 是否已经触发了取消信号;
    • 如果已经被取消,child 会立刻被取消;
    • 如果没有被取消,child 会被加入 parent 的 children 列表中,等待 parent 释放取消信号;
  • 当父上下文是开发者自定义的类型、实现了 context.Context 接口并在 Done() 方法中返回了非空的管道时;
    • 运行一个新的 Goroutine 同时监听 parent.Done() 和 child.Done() 两个 Channel;
    • 在 parent.Done() 关闭时调用 child.cancel 取消子上下文;

propagateCancel 的作用是在 parent 和 child 之间同步取消和结束的信号,保证在 parent 被取消时,child 也会收到对应的信号,不会出现状态不一致的情况。

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	done := parent.Done()
    // 如果 done 为 nil 说明这个ctx是不可取消的
    // 如果 done == closedchan 说明这个ctx不是标准的 cancelCtx,可能是自定义的
	if  done == closedchan || done == nil {
		return nil, false
	}
    // 然后调用 value 方法从ctx中提取出 cancelCtx
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
	if !ok {
		return nil, false
	}
    // 最后再判断一下cancelCtx 里存的 done 和 父ctx里的done是否一致
    // 如果不一致说明parent不是一个 cancelCtx
	pdone, _ := p.done.Load().(chan struct{})
	if pdone != done {
		return nil, false
	}
	return p, true
}

ancelCtx 的 done 方法会返回一个 chan struct{}

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{})
}
var closedchan = make(chan struct{})

parentCancelCtx 其实就是判断 parent context 里面有没有一个 cancelCtx,有就返回,让子context可以“挂靠”到parent context 上,如果不是就返回false,不进行挂靠,自己新开一个 goroutine 来监听。

3.4 timerCtx

timerCtx 内部不仅通过嵌入 cancelCtx 的方式承了相关的变量和方法,还通过持有的定时器 timer 和截止时间 deadline 实现了定时取消的功能:

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

	deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
	return c.deadline, true
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

3.5 valueCtx

valueCtx 是多了 key、val 两个字段来存数据:

type valueCtx struct {
	Context
	key, val interface{}
}

取值查找的过程,实际上是一个递归查找的过程:

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

如果 key 和当前 ctx 中存的 value 一致就直接返回,没有就去 parent 中找。最终找到根节点(一般是 emptyCtx),直接返回一个 nil。所以用 Value 方法的时候要判断结果是否为 nil,类似于一个链表,效率是很低的,不建议用来传参数。

4. 使用建议

在官方博客里,对于使用 context 提出了几点建议:

  • 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
  • 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
  • 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
  • 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

到此这篇关于Go语言上下文context底层原理的文章就介绍到这了,更多相关Go context 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言上下文context底层原理

本文链接: https://www.lsjlt.com/news/150836.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Go语言上下文context底层原理
    目录1. context 介绍2. 基本介绍3. 源码分析3.1 Context 接口3.2 emptyCtx3.3 cancelCtx3.4 timerCtx3.5 valueCt...
    99+
    2022-11-13
  • Go语言context上下文管理的使用
    目录context 有什么作用传递共享的数据取消 goroutine防止 goroutine 泄漏context.Value 的查找过程是怎样的context 有什么作用 conte...
    99+
    2022-11-13
  • go语言中slice,map,channl底层原理
    目录0. 前序1. slice1.1 slice的创建1.2 数据结构1.3 扩容机制2. map2.1 map创建2.2 数据结构2.3 扩容机制3. channl3.1 数...
    99+
    2022-06-07
    map GO slice go语言
  • Go语言的context上下文管理怎么使用
    这篇文章主要讲解了“Go语言的context上下文管理怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言的context上下文管理怎么使用”吧!context 有什么作用cont...
    99+
    2023-06-29
  • Go语言中的并发goroutine底层原理
    目录一、基本概念①并发、并行区分②从用户态线程,内核态线程阐述go与java并发的优劣②高并发为什么是Go语言强项?③Go语言实现高并发底层GMP模型原理解析二、上代码学会Go语言并...
    99+
    2022-11-13
  • Go语言底层原理互斥锁的实现原理
    目录Go 互斥锁的实现原理?概念使用场景底层实现结构操作加锁解锁Go 互斥锁正常模式和饥饿模式的区别?正常模式(非公平锁)饥饿模式(公平锁)Go 互斥锁允许自旋的条件?Go 互斥锁的...
    99+
    2022-11-11
  • go语言中slice,map,channl底层原理是什么
    今天小编给大家分享一下go语言中slice,map,channl底层原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。...
    99+
    2023-06-30
  • Go语言中并发goroutine底层原理的示例分析
    小编给大家分享一下Go语言中并发goroutine底层原理的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、基本概念①并发、并行区分1.概念并发:同一时间段内一个对象执行多个任务,充分利用时间并行:同一时刻,多个...
    99+
    2023-06-29
  • Go语言IO操作输入输出底层原理及文件操作API
    目录1.输入输出的底层原理2. 文件操作相关API3. 打开和关闭文件4. 写文件5. 读文件6. 拷贝文件7. bufio1.输入输出的底层原理 终端其实是一个文件,相关实例如下...
    99+
    2023-01-08
    Go IO操作输入输出API Go IO API
  • Go语言CSP并发模型goroutine及channel底层实现原理
    目录Go的CSP并发模型(goroutine + channel)1、goroutinegoroutine的优点:2、channel无缓存channel有缓存channel3、Go并...
    99+
    2022-11-13
  • C语言函数调用底层实现原理分析
    目录前言寄存器分配寄存器使用约定栈帧结构堆栈操作函数调用流程函数调用常用命令函数调用约定常见调用约定cdecl调用约定stdcall调用约定(微软命名)fastcall调用约定thi...
    99+
    2023-02-24
    C语言函数调用 C语言函数 函数底层实现原理
  • 深入了解Go语言内存管理的底层机制
    Go语言的内存管理是基于垃圾回收的机制,它使用了一个称为Go垃圾回收器的组件来自动管理内存的分配和释放。Go垃圾回收器使用了一个基于...
    99+
    2023-10-08
    Golang
  • 汇编语言基础理解计算机底层技术原理
    目录前言1.机器语言2.进制思想本质3.二进制我们为什么学习理解二进制?4.数据宽度5.有符号数和无符号数6.原码反码补码7.位运算8.位运算的加减乘除9.汇编语言环境说明10.寄存...
    99+
    2022-11-12
  • C语言函数调用底层实现原理是什么
    本文小编为大家详细介绍“C语言函数调用底层实现原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“C语言函数调用底层实现原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。前言C语言程序执行实质上的函数...
    99+
    2023-07-05
  • java语言自行实现ULID过程底层原理详解
    目录前提ULID出现的背景ULID规范组成排序规范的表示形式编码单调性溢出错误处理二进制布局ULID使用实现ULIDULID性能评估小结前提 最近发现各个频道推荐了很多ULID相关文...
    99+
    2022-11-13
    java ULID底层原理 java ULID
  • 深入解析Go语言垃圾回收器管理的底层
    Go语言使用了一种称为“三色标记”的垃圾回收算法来管理内存的分配和回收。这个算法基于一个简单的原则:将所有的对象分为三种颜色,分别是...
    99+
    2023-10-08
    Golang
  • 总结C语言指针从底层原理到花式技巧
    这篇文章主要介绍“总结C语言指针从底层原理到花式技巧”,在日常操作中,相信很多人在总结C语言指针从底层原理到花式技巧问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”总结C语言指针从底层原理到花式技巧”的疑惑有所...
    99+
    2023-06-15
  • GO语言原生实现文件上传功能
    本文实例为大家分享了GO原生实现文件上传功能的具体代码,供大家参考,具体内容如下 写在前面 最近在学习go,发现实践才是检验真理的唯一标准。在不引入任何框架的基础上,利用go语言实现...
    99+
    2022-11-11
  • Go 语言实现 HTTP 文件上传和下载
    前言: 近我使用 Go 语言完成了一个正式的 Web 应用,有一些方面的问题在使用 Go 开发 Web 应用过程中比较重要。过去,我将 Web 开发作为一项职业并且把使用不同的语言和...
    99+
    2022-11-11
  • 汇编语言中计算机底层技术原理的示例分析
    这篇文章将为大家详细讲解有关汇编语言中计算机底层技术原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.机器语言何为语言,就是人和人之间交流的工具。而汇编语言就是计算机的语言。机器语言(二进制...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作