Go Context的简单使用与底层原理

什么是context?

context(上下文)可以理解为是承接上下文的载体,可以被无限传递下去,Go中的context负责存放协程的当前信息(快照),其中包含着协程中的变量信息及函数调用。


context的使用场景

后端接收请求时,有时要将获取到的数据交由多个协程处理。例如登录验证时,将权限验证、密码验证、有效期验证分到三个不同的协程里处理,如果此时有一个协程处理失败了,其他协程也应该立即关闭,避免持续占用系统资源。而在Go中就可以用context来进行控制操作。


context的底层结构

// Context提供跨越API的截止时间获取,取消信号,以及请求范围值的功能。
// 它的这些方案在多个goroutine中使用是安全的
type Context interface {

	// 如果设置了截止时间,这个方法ok会是true,并返回设置的截止时间
	Deadline() (deadline time.Time, ok bool)

    // 如果Context超时或者主动取消就返回一个关闭的channel,如果返回的是nil,表示这个context永远不会关闭,例如:Background()
	Done() <-chan struct{}

    // 返回发生的错误
	Err() error

    // 它的作用就是传值,保存context的相关数据
	Value(key interface{}) interface{}
}

Done方法在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回。


context的调用

context的调用是链式/树状的,如果一个节点被取消,该节点旗下的所有子context都将会被取消。


如何创建一个context树

创建一个context根节点(Background)
// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
func Background() Context

Backgound返回的Context是所有context的root,不能够被cancel。

该Context通常由接收request的第一个goroutine创建,它不能被取消、没有值、也没有过期时间,常作为处理request的顶层context存在。

创建一个继承父节点的context(WithCancel、WithDeadline、WithTimeout、WithValue)
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

WithXXX返回一个继承父节点的Context,这个Context在父Context的Done被关闭时关闭自己的Done通道,或者在自己被Cancel的时候关闭自己的Done。
WithXXX同时还返回一个取消函数cancel,这个cancel用于取消当前的Context。


context简单使用示例

import (
	"context"
	"fmt"
	"time"
)

func main() {
	//创建一个context根节点
	root := context.Background()

	//根据根节点派生出子节点
	ctx,cancel := context.WithCancel(root)

	//开启协程
	go test(ctx)

	//十秒后取消子节点context
	time.Sleep(time.Second * 10)
	cancel()

	time.Sleep(time.Second * 1)
}

//将context作为参数传入函数
func test(ctx context.Context)  {
	//循环,每秒检测一次当前context是否被取消
	for {
		time.Sleep(1 * time.Second)
		select {
		//如果检测到当前context被取消(通道被关闭)
		case <-ctx.Done():
			//关闭协程
			fmt.Println("done")
			return
		default:
			//正常运行
			fmt.Println("work")
		}
	}
}
  • 这里涉及到一个select知识点:
    for循环select时,如果其中一个case的通道已经关闭,则每次都会执行到这个case(如果chan关闭前,通道内元素已经被读完,接下来所有接收的值都会非阻塞直接成功,返回 chan元素的零值,但是第二个bool值一直为false)。
    如果select里边只有一个case,而这个case被关闭了,则会出现死循环。

运行结果

work
work
work
work
work
work
work
work
work
done

Process finished with the exit code 0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值