Go的并发如何配合上下文(ctx Context)使用? [_]~( ̄▽ ̄)~* Go相关


说明: 上下文的英文是context, 其英文简写约定俗成是 ctx

上下文处理

上下文在go中有一个约定俗成的写法ctx, 如果你用过python应该知道self, 这是python类初始化的一个对象, 在python的类中任何函数都可以去读取和使用它, 并且会把它当作第一个参数传入。而ctx也有点类似self, 他也是一个约定俗成的叫法, 并没有强制性, 并且如果使用,一般也是作为函数的第一个变量传入, 一般我们在go的主进程中生成上下文, 并且所有需要的go程都可以获取上下文。

创建上下文

在Go中, 上下文除了传递消息对象外, 还有一个非常重要的作用, 就是用它来传递取消关闭信号。特别是在众多Go程中, 我们可以用上下文来同时控制其的运行状态(开启或结束)。

上下文中, 内置了四个函数, 可以对已有的上下文进行操作

  1. Deadline() (过期时间 time.Time, ok bool): 查看是否有过期时间, 如果有就返回过期时间
  2. Done() <-chan struct{}: 用于做退出判断, 如果过期时间或者持续时间结束, 或手动执行CancelFunc, 则会向此处传入一个空结构体。
  3. Err() error: 用来解释退出的原因。
  4. Value(key any) any: 用来读取上下文中的值。

我们常用创建上下文的方法常用的有五种

  1. context.Background() Context: 一般我们最底层创建上下文会用到这个, 可以看出获取一个空上下文的方法。它及不包含信息, 也没有传递关闭信号的功能。
  2. context.WithCancel(父级上下文 Context) (Context, CancelFunc): 这算是比较常规的上下文, 他会返回当前的上下文和一个取消函数。
func 常规上下文() {
	上下文, 常规取消 := context.WithCancel(空上下文)
	获取过期时间, ok := 上下文.Deadline()
	if ok {
		fmt.Println("过期时间为:", 获取过期时间)
	} else {
		fmt.Println("未设置过期时间")
	}
	for i := 0; i < 2; i++ {
		select {
		case <-time.After(time.Second):
			常规取消()
			fmt.Println("执行取消")
		case 取消信息 := <-上下文.Done():
			fmt.Println("已取消:", 取消信息, 上下文.Err())
		}
	}
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	常规上下文()
}

在这里插入图片描述
3. context.WithDeadline(父级上下文 Context, 过期时间 time.Time) (Context, CancelFunc): 这种上下文可以设置一个过期时间, 在到达过期时间的时候让相关进程结束, 比如我们可以给一个用户的登录信息放在上下文中, 并设置过期时间, 设置过过期时间的上下文可以从上下文.Deadline()获取到截止日期

func 过期上下文(过期时间 time.Time) {
	上下文, 主动取消 := context.WithDeadline(空上下文, 过期时间)
	defer 主动取消()
	for {
		select {
		case <-time.After(1 * time.Second):
			获取过期时间, ok := 上下文.Deadline()
			if ok {
				fmt.Println("过期时间为:", 获取过期时间)
			}
			fmt.Println("正常执行中!")
		case <-上下文.Done():
			fmt.Println("已取消:", 上下文.Err())
			return
		}
	}
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	时间 := time.UnixMicro(time.Now().UnixMicro() + (time.Second.Microseconds() * 3))
	过期上下文(时间)
}

在这里插入图片描述
4. context.WithTimeout(父级上下文 Context, 持续时间 time.Duration) (Context, CancelFunc): 这样用法和上述过期时间类似, 只不过把过期时间改为了倒计时的形式, 这种方法通用可以从上下文.Deadline()获取到截止日期

func 超时上下文(倒计时 time.Duration) {
	上下文, 取消 := context.WithTimeout(空上下文, 倒计时)
	上下文.Deadline()
	defer 取消()
	for {
		select {
		case <-time.After(1 * time.Second):
			获取超时时间, ok := 上下文.Deadline()
			if ok {
				fmt.Println("超时时间: ", 获取超时时间)
			}
			fmt.Println("正常执行中")
		case <-上下文.Done():
			fmt.Println(上下文.Err())
			return
		}
	}
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	计时 := 3 * time.Second
	超时上下文(计时)
}

在这里插入图片描述
5. context.WithValue(父级上下文, 键, 值 any) Context: 用于上下文传递信息保存键值对, 但要注意的是, 上下文一般传播范围会非常广, 为了尽量节省资源, 务必只传入一些必要的信息, 一些可选信息就没有必要进行传播了。比如用户ID, 用户key, 系统os类型等程序启动后基本不会变更的数据, 运行时参数的各种杂项数据是不推荐放入上下文的。

func 传值上下文(, 值 any) {
	上下文 := context.WithValue(空上下文,,)
	fmt.Println("当前上下文的键为: ", 上下文.Value())
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	传值上下文("我是键", "寻觅")
}

PS: 除此之外, 还有一个context.TODO() Context 这个方法在代码层面和context.Background() Context实现的东西是一模一样的, 但TODO主要是用来占位的, 可以看作为一个占位符, 而Background则是可以看成一个空的上下文, 用来做我们的创建模板用的。

上下文继承

Go上下文的继承很好理解, 父级会影响子级, 父级一旦关闭, 所有子级也都会随之关闭, 子集的值会覆盖父级, 但子集的过期时间和持续时间是会被父级影响的。
但上下文关闭不会影响使用Value(key any) any取值, 父级上下文关闭后, 子集上下文还是可以正常取值

func 上下文传递测试() {
	上下文1, 取消 := context.WithCancel(空上下文)
	defer 取消()
	上下文2 := context.WithValue(上下文1, "测试键", "上下文1的键")
	上下文3, _ := context.WithTimeout(上下文2, 5*time.Second)
	for {
		select {
		case <-time.After(2 * time.Second):
			fmt.Println("上下文1取消前的上下文3: ", 上下文3.Value("测试键"))
			取消()
			fmt.Println("上下文1 已被取消")
			fmt.Println("上下文1取消前的上下文3: ", 上下文3.Value("测试键"))
		case <-上下文3.Done():
			fmt.Println("上下文1: ", 上下文1.Err(), <-上下文1.Done())
			fmt.Println("上下文2: ", 上下文2.Err(), <-上下文2.Done())
			fmt.Println("上下文3: ", 上下文3.Err(), <-上下文3.Done())
			return
		}
	}
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	上下文传递测试()
}

在这里插入图片描述

线程安全

这里的线程安全是指, 多个Go程同时操作上下文, 进行读写等操作, 是否会出现异常。首先, Go的上下文并没有提供一个写Set接口, 而只有一个类似读Value(键)的接口, 所有数据的写入操作都在上下文定义的那一刻就决定了。
想要变更数据, 就只能通过创建子上下文, 在子上下文中添加新值, 或覆盖老值, 但父上下文中的值是不会改变的。不只是父子上下文, 所有上下文中的数据都是独立的, 互不影响的

func 创建上下文(上下文 context.Context,string) context.Context {
	for {
		_ = context.WithValue(上下文, "测试键",)
	}
}

func 上下文线程安全() {
	上下文 := context.WithValue(context.Background(), "测试键", "---")
	go 创建上下文(上下文, "+++")
	go 创建上下文(上下文, "***")
	go func() {
		for {
			fmt.Println(上下文.Value("测试键"))
			time.Sleep(time.Second)
		}
	}()
	time.Sleep(time.Second * 10)
}

func main() {
	fmt.Println("开始时间: ", time.Now())
	上下文线程安全()
}

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻_觅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值