[Golang] Context

news/2024/9/19 9:21:21 标签: golang, 开发语言, 后端

[Golang] Context

文章目录

  • [Golang] Context
    • 什么是context
    • 创建context
      • 创建根context
      • 创建context
    • context的作用
      • 并发控制
      • context.WithCancel
      • context.WithDeadline
      • context.WithTimeout
      • context.WithValue

什么是context

Golang在1.7版本中引入了一个标准库的接口context,定义:

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

它定义了四个方法:

  • Deadline:设置context.Context被取消的时间,即截止日期

  • Done:返回一个只读channel,当Context到达截止日期时或被取消,这个channel就会被关闭,表示Context的链路结束,多次调用Done会返回同一个channel

  • Err:返回Context结束的原因,它只会在Done返回的channel被关闭时,才会返回非空的值;

    • 情况1:Context被取消:返回Canceled
    • 情况2:Context超时:返回DeadlineExceeded
  • Value:从context.Context中获取键对应的值,类似与map的get方法,对于同一个Context,多次调用Value并传入相同的key会返回相同的结果,如果没有对应的key就返回nil。

    • 键值对通过WithValue方法写入
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}
}

创建context

创建根context

两种方法:

  • context.Background()
  • context.TODO()

两者没有什么太多的区别,都是创建根context,根context是一个空的context,不具备任何功能。

一般情况下,当前函数没有上下文作为入参,我们就使用context.Background()创建一个根context作为起始的上下文向下传递。

创建context

根context被创建后,不具备任何功能,为了让context在程序中发挥作用,我们需要依靠包提供的With系列函数来进行派生。

四个派生函数:

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

基于当前context,每个With函数都会创建出一个新的context,这类似于我们熟悉的树结构,当前context称为父context,派生出的每个新context被称为子context。

image-20240918180659297

通过根context的四个With函数派生出四种类型的context,每种context又可以通过同样的方式调用with系列方法继续向下派生出新的context,整体结构像一个树一样。

context的作用

  • 用于并发控制,控制协程的退出
  • 上下文信息的传递

总的来说,就是用来在父子goroutine间进行值传递和发生cancel信号的一种机制。

并发控制

一般的服务器都是一直运行的,等待客户端或者浏览器的请求做出响应,思考这种场景,一个微服务架构中下,服务器收到一个请求后,并不会在一个goroutine下完成(如果逻辑复杂),而是创建很多goroutine共同完成这个请求。

假设有rpc1—rpc2—rpc3—rpc4—rpc5,5个rpc调用。

但是如果在整个rpc调用中,如果rpc1就出现了错误,如果没有context存在,服务器就会坚持调用完整个流程,也就是等待所有rpc调用完成后才能返回结果,但是实际上这样浪费了不少的时间,单纯浪费计算和IO资源(rpc1错误之后的rpc调用都是无用功)。因为rpc调用之间不知道已经产生了错误,而context就很好的解决了这个问题。

在不需要子goroutine继续执行的时候,通过context通知子goroutine关闭即可。

context.WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

context.WithCancel函数是一个取消控制函数,只需要一个context作为参数,能够衍生出一个新的子context和取消函数Cancel,我们可以通过这个将这个子context传入子goroutine中,执行Cancel函数来关闭这个子goroutine,当前的上下文和它的子上下文都会被取消,所有的goroutine都会同步收到取消信号。

示例:

package main

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

func main() {
	fmt.Println()
	ctx, cancel := context.WithCancel(context.Background())
	go watch(ctx, "goroutine1")
	go watch(ctx, "goroutine2")
	time.Sleep(3 * time.Second)
	fmt.Println("end!!!")
	cancel()
	time.Sleep(time.Second)
}
func watch(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name, " exit")
			return
		default:
			fmt.Println(name, " watching")
			time.Sleep(time.Second)
		}
	}
}

执行结果:

image-20240918184141136

通过WithCancel函数派生出一个带有返回函数cancel的ctx:ctx, cancel := context.WithCancel(context.Background()),并且把ctx传入子goroutine中,在3秒内没有执行cancel,子goroutine将一直执行default语句,3秒后,执行cancel,此时子goroutine从ctx.Done()收到消息,执行return结束。

context.WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	return WithDeadlineCause(parent, d, nil)
}

context.WithDeadline函数也是一个取消控制函数,共有两个参数,一个是context,另一个是截止时间,同样会返回一个子context和取消函数cancel。在使用时,如果没有到截止日期,我们可以通过调用cancel函数来手动取消context,控制goroutine的退出,如果到了截止日期,我们都没有调用cancel函数,子context的Done()管道也会收到一个取消信号,来控制子goroutine的退出。

示例:

package main

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

func main() {
	fmt.Println()
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
	defer cancel()
	go watch(ctx, "goroutine1")
	go watch(ctx, "goroutine2")
    // 让goroutine1和goroutine2先执行5秒
	time.Sleep(5 * time.Second)
	fmt.Println("end!!!")

}
func watch(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done()://但是不到5秒,3秒时收到了退出信号
			fmt.Println(name, " exit")
			return
		default:
			fmt.Println(name, " watching")
			time.Sleep(time.Second)
		}
	}
}

执行结果:

image-20240918210655210

我们并没有调用cancel函数,但是在过了3秒后,子goroutine里ctx.Done()收到了信号,子goroutine进行退出。

context.WithTimeout

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

context.WithTimeoutcontext.WithDeadline差不多,都是用于超时取消子context,只是第二个参数有点区别,不是具体时间,而是时间长度。

示例:

package main

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

func main() {
	fmt.Println()
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	go watch(ctx, "goroutine1")
	go watch(ctx, "goroutine2")
    // 让goroutine1和goroutine2先执行5秒
	time.Sleep(5 * time.Second)
	fmt.Println("end!!!")

}
func watch(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done()://但是不到5秒,3秒时收到了退出信号
			fmt.Println(name, " exit")
			return
		default:
			fmt.Println(name, " watching")
			time.Sleep(time.Second)
		}
	}
}

执行结果:

image-20240918211051169

执行结果和context.WithDeadline类似。

context.WithValue

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

context.WithValue函数从父context中创建一个子context用于传值,函数参数是父context、key、val,返回一个context。一般用于上下文信息的传递,比如请求唯一id,以及trace_id,用于链路追踪以及配置穿透。

示例:

package main

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

func main() {
	fmt.Println()
	ctx := context.WithValue(context.Background(), "name", "张三")
	go func1(ctx)
	time.Sleep(time.Second)
}
func func1(ctx context.Context) {
	fmt.Println("name = ", ctx.Value("name").(string))
}

执行结果:

image-20240918211613966


http://www.niftyadmin.cn/n/5665332.html

相关文章

Java服务端数据库连接:连接池的故障恢复策略

Java服务端数据库连接&#xff1a;连接池的故障恢复策略 大家好&#xff0c;我是微赚淘客返利系统3.0的小编&#xff0c;是个冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在Java服务端应用中&#xff0c;数据库连接池是核心组件之一&#xff0c;它管理着数据库…

【Python】探索Magenta:音乐与艺术的机器智能创作

下班了&#xff0c;今天的苦就先吃到这里。 在人工智能的浪潮中&#xff0c;机器学习技术正逐渐渗透到艺术创作的各个领域。今天&#xff0c;我们来探索一个特别的项目——Magenta&#xff0c;它是由Google Brain团队发起的&#xff0c;旨在使用机器智能生成音乐和艺术。这个项…

MySQL|MySQL 中 `DATE_FORMAT()` 函数的使用

文章目录 概述方法签名格式化字符基本用法实际应用案例示例1&#xff1a;显示日期和星期几示例2&#xff1a;仅显示日期示例3&#xff1a;按周统计订单数量 注意事项结论 概述 DATE_FORMAT() 是 MySQL 中的一个内置函数&#xff0c;用于格式化日期和时间数据。它可以根据指定的…

【无人机设计与控制】基于粒子群算法的三维无人机航迹规划

摘要 本文研究了基于粒子群算法&#xff08;PSO&#xff09;的三维无人机航迹规划问题。通过粒子群优化算法&#xff0c;无人机能够在复杂三维环境中进行路径规划&#xff0c;以避开障碍并实现最优路径飞行。该方法有效结合了无人机的飞行动力学特性与环境约束&#xff0c;能够…

模型验证 — 交叉验证Cross Validation的方法

目录 简单交叉验证 K折交叉验证 留一法交叉验证 留P法交叉验证 ShuffleSplit StratifiedShuffleSplit StratifiedKFold 交叉验证(Cross Validation)是验证模型性能的一种统计分析方法,其基本思想是在某种意义下将原始数据进行分组,一部分作为模型的训练数据集…

[PyVista] 介绍

一,介绍 PyVista是一个用于3D可视化和网格处理的Python库&#xff0c;它提供了一种简单而强大的方式来创建、渲染和分析复杂的三维数据集和网格。PyVista建立在VTK&#xff08;Visualization Toolkit&#xff09;和NumPy之上&#xff0c;为用户提供了许多方便的工具和功能&…

Linux 使用 tar 命令

1 使用 gzip 压缩&#xff0c;将 dir 目录压缩 tar -zcvf archive.tar.gz dir/ 2 解压 tar.gz 文件 tar -zxvf archive.tar.gz 3 解压 tar.gz 文件&#xff0c;到指定 dir 目录 tar -zxvf archive.tar.gz -C dir 参数说明&#xff1a; -z 支持gzip解压文件 -x 从压缩的…

第五部分:2---信号的介绍、产生、处理

目录 信号的概念&#xff1a; 信号表的继承&#xff1a; 信号的分类与编号&#xff1a; 特殊的信号&#xff1a; 信号的产生&#xff1a; 1.键盘输入&#xff1a; 2.系统调用&#xff1a; 3.异常或硬件错误&#xff1a; 4.总结&#xff1a; 信号的处理&#xff1a; …