您的当前位置:首页正文

更便捷的goroutine控制利器- Context

2024-11-09 来源:个人技术集锦

首先要和大家说声抱歉哈,由于工作上、生活上的某些琐事,以至于造成本节的断更。不过请不要悲伤。因为我在这期间也是做过详细的复习的。我相信一定会让你有更加深入的理解,同时也欢迎你向我提出不足。我们共同进步。话不多说,我相信你已经迫不及待了。还在等什么?let‘s GO

在本文中,我首先会介绍context是什么,它有什么作用,以及如何使用,其中还会参杂一点个人的理解,以及部分源码的了解。What are you waiting for?

Context:

Context包定义了上下文类型,该类型在API边界之间以及进程之间传递截止日期,取消信号和其他请求范围的值

对服务器的传入请求应创建一个Context,而对服务器的传出调用应接受一个Context。

它们之间的函数调用链必须传播Context,可以选择将其替换为使用WithCancel,WithDeadline,WithTimeout或WithValue创建的派生Context。取消上下文后,从该上下文派生的所有上下文也会被取消。

WithCancel,WithDeadline和WithTimeout函数采用Context(父级)并返回派生的Context(子级)和CancelFunc。调用CancelFunc会取消该子代及其子代,删除父代对该子代的引用,并停止所有关联的计时器。未能调用CancelFunc会使子代及其子代泄漏,直到父代被取消或计时器触发。审核工具检查所有控制流路径上是否都使用了CancelFuncs。

使用上下文的程序应遵循以下规则,以使各个包之间的接口保持一致,并使静态分析工具可以检查上下文传播:

不要将上下文存储在结构类型中;而是将上下文明确传递给需要它的每个函数。 Context应该是第一个参数,通常命名为ctx:

func DoSomething(ctx context.Context, arg Arg) error {
//  ... use ctx ...
}

即使函数允许,也不要传递nil Context。如果不确定使用哪个上下文,请传递context.TODO

仅将上下文值用于传递过程和API的请求范围数据,而不用于将可选参数传递给函数。

可以将相同的上下文传递给在不同goroutine中运行的函数。上下文可以安全地被多个goroutine同时使用

巴拉巴拉,说了一大堆,反正我一句没懂,当然我知道context是干嘛的,(尬~,不小心暴露了,学渣的本质),说说我的理解以及使用建议

  • 对服务器的传入请求应创建一个Context,而对服务器的传出响应也应接受一个Context。

  • 函数调用链必须传播Context,也可以选择将其替换为使用WithCancel,WithDeadline,WithTimeout或WithValue创建的派生Context(也就是子类context)。取消上下文后,从该上下文派生的所有上下文也会被取消

  • Context 不要放在结构体中,要以参数的方式传递。

  • Context 作为函数的参数时,要放在第一位,也就是第一个参数。

  • 要使用 context.Background 函数生成根节点的 Context,也就是最顶层的 Context。

  • Context 传值要传递必须的值,而且要尽可能地少,不要什么都传。

  • Context 多协程安全,可以在多个协程中放心使用。

go Context定义

Context 是Go 1.7 标准库引入 的标准库,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。

使用context,我们可以轻松优雅的做到取消goroutine超时时间,运行截止时间k-v存储等。它是并发安全的

随着 context 包的引入,标准库中很多接口因此加上了 context 参数,例如 database/sql 包。context 几乎成为了并发控制和超时控制的标准做法。

context.Context 类型的值可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。
与它协作的 API 都可以由外部控制执行“取消”操作,例如:取消一个 HTTP 请求的执行。

止于这些么?当然 不止,还有更多的骚操作,接下来让我们一起拿下它吧。

引入

为什么需要使用context,理由一

一个协程启动后,大部分情况需要等待里面的代码执行完毕,然后协程会自行退出。但需要让协程提前退出怎么办呢?

下面我们以一个小的示例,来逐渐了解context的妙用之一吧

package main

import (
	"fmt"
	"sync"
	"time"
)

func monitor(name string) {
	//开启for select循环,j进行后台监控
	for {
		select {
		default:
			fmt.Printf("Time: %v 监控者:%s, 正在监控...\n", time.Now().Unix(), name)
		}
		// sleep 1 second
		time.Sleep(time.Second * 5)
		fmt.Printf("%s 监控完成,一切正常,请指示 over...\n", name)
	}
}

func main() {
	var wg sync.WaitGroup
	// Define waiting group
	wg.Add(1)
	go func() {
		// Execution complete
		defer wg.Done()
		monitor("天眼")
	}()
	//Exit after waiting
	wg.Wait()
}

我们在这里实现了一个基本的groutine执行的case

我们定义了等待组wait group,防止协程提前退出。关于wait group可参考上一篇文章,golang并发控制的心应手。

他会周期性的运行,不断打印监控信息,例如

Top