传统方式
在刚开始学go的时候,没用过Context包,那么退出携程的方式一般有这么几种
使用携 chan 发送消息通知,这种一般只适合单个goroutine
func exit01() {
done := make(chan bool)
go func() {
for {
select {
case <-done:
fmt.Println("退出携程")
return
default:
fmt.Println("监控中...")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(3 * time.Second)
done <- true
time.Sleep(5 * time.Second)
fmt.Println("程序退出")
}
使用关闭 chan 的方式通知多个goroutine退出
func exit02() {
done :=make(chan bool)
go func() {
for{
select {
case <-done:
fmt.Println("退出携程01")
return
default:
fmt.Println("监控01...")
time.Sleep(1*time.Second)
}
}
}()
go func() {
for res :=range done{ //没有消息阻塞状态,chan关闭 for 循环结束
fmt.Println(res)
}
fmt.Println("退出监控03")
}()
go func() {
for{
select {
case <-done:
fmt.Println("退出携程02")
return
default:
fmt.Println("监控02...")
time.Sleep(1*time.Second)
}
}
}()
time.Sleep(3*time.Second)
close(done)
time.Sleep(5*time.Second)
fmt.Println("退出程序")
}
初识 Context包
一个用于手动控制 goroutine 退出或者结束
获取 context上下文两种方式
ctx := context.Background() //这只能用于高等级(在 main 或顶级请求处理中)。这能用于派生我们稍后谈及的其他 context
ctx := context.TODO() // 也只能用于高等级或当您不确定使用什么 context,或函数以后会更新以便接收一个 context
他们的底层实现完全一致,不同的是,静态分析工具可以使用它来验证 context 是否正确传递,
这是一个重要的细节,因为静态分析工具可以帮助在早期发现潜在的错误,并且可以连接到 CI/CD 管道
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
使用context.WithTimeout,主动调用 cancel()方法,可以在时间超时之前退出 goroutine
func exit03() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
go func() {
for{
select {
case <-ctx.Done():
fmt.Println("退出携程")
return
default:
fmt.Println("请求中..")
time.Sleep(1*time.Second)
}
}
}()
time.Sleep(5*time.Second)
//cancel() //也可以手动调用 cancel()方法退出
//time.Sleep(2*time.Second)
fmt.Println("程序退出")
}
使用 context.WithCanel()方法,根据外部条件手动调用 cancel()方法退出
只有创建它的函数才能调用取消函数来取消此 context。如果您愿意,可以传递取消函数,但是,强烈建议不要这样做。
这可能导致取消函数的调用者没有意识到取消 context 的下游影响。可能存在源自此的其他 context,
这可能导致程序以意外的方式运行。简而言之,永远不要传递取消函数
func exit04() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for{
select {
case <-ctx.Done():
fmt.Println("退出携程")
return
default:
fmt.Println("监控01")
time.Sleep(1*time.Second)
}
}
}()
time.Sleep(5*time.Second)
cancel()
time.Sleep(2*time.Second)
fmt.Println("退出程序")
}
使用 context.WithDeadLine() ,在指定的时间退出 goroutine
func exit05() {
stringTime := "2019-08-11 09:08:01"
loc, _ := time.LoadLocation("Local")
the_time, _ := time.ParseInLocation("2006-01-02 15:04:05", stringTime, loc)
ctx, _ := context.WithDeadline(context.Background(), the_time)
go func() {
for{
select {
case <-ctx.Done():
fmt.Println("退出 goroutine")
return
default:
fmt.Println("监控...")
time.Sleep(1*time.Second)
}
}
}()
time.Sleep(60*time.Second)
fmt.Println("程序退出")
}
使用context.WithValue()传值,在所有的context树中都能获取到该值,如果设置相同的key 则覆盖该值
不建议使用 context 值传递关键参数,而是函数应接收签名中的那些值,使其显式化。
func exit06() {
ctx := context.WithValue(context.Background(), "msg", "hello word")
go func(ctx context.Context) {
fmt.Println(ctx.Value("msg"))
}(ctx)
time.Sleep(2*time.Second)
fmt.Println("程序退出")
}
相关建议约束
- context.Background 只应用在最高等级,作为所有派生 context 的根。
- context.TODO 应用在不确定要使用什么的地方,或者当前函数以后会更新以便使用 context。
- context 取消是建议性的,这些函数可能需要一些时间来清理和退出。
- context.Value 应该很少使用,它不应该被用来传递可选参数。这使得 API 隐式的并且可以引起错误。取而代之的是,这些值应该作为参数传递。
- 不要将 context 存储在结构中,在函数中显式传递它们,最好是作为第一个参数。
- 永远不要传递不存在的 context 。相反,如果您不确定使用什么,使用一个 ToDo context。
- Context 结构没有取消方法,因为只有派生 context 的函数才应该取消 context。
参考:https://studygolang.com/articles/13866?fr=sidebar
原文地址:https://www.cnblogs.com/nirao/p/11333990.html