golang之context
context 用来解决 goroutine 之间退出通知、元数据传递的功能。
context 使用起来非常方便。源码里对外提供了一个创建根节点 context 的函数:
func Background() Context
background 是一个空的 context, 它不能被取消,没有值,也没有超时时间。
有了根节点 context,又提供了四个函数创建子节点 context:
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
注意:
- 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
- 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
- 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
- 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。
传递共享的数据
对于 Web 服务端开发,往往希望将一个请求处理的整个过程串起来,这就非常依赖于 Thread Local(对于 Go 可理解为单个协程所独有) 的变量,而在 Go 语言中并没有这个概念,因此需要在函数调用的时候传递 context。
package main import (
"context"
"fmt"
) func main() {
ctx := context.Background()
process(ctx) ctx = context.WithValue(ctx, "traceId", "qcrao-2019")
process(ctx)
} func process(ctx context.Context) {
traceId, ok := ctx.Value("traceId").(string)
if ok {
fmt.Printf("process over. trace_id=%s\n", traceId)
} else {
fmt.Printf("process over. no trace_id\n")
}
}
结果
process over. no trace_id
process over. trace_id=qcrao-2019
第一次调用 process 函数时,ctx 是一个空的 context,自然取不出来 traceId。第二次,通过 WithValue 函数创建了一个 context,并赋上了 traceId 这个 key,自然就能取出来传入的 value 值。
当然,现实场景中可能是从一个 HTTP 请求中获取到的 Request-ID。所以,下面这个样例可能更适合:
const requestIDKey int = 0
func WithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(
func(rw http.ResponseWriter, req *http.Request) {
// 从 header 中提取 request-id
reqID := req.Header.Get("X-Request-ID")
// 创建 valueCtx。使用自定义的类型,不容易冲突
ctx := context.WithValue(
req.Context(), requestIDKey, reqID)
// 创建新的请求
req = req.WithContext(ctx)
// 调用 HTTP 处理函数
next.ServeHTTP(rw, req)
}
)
}
// 获取 request-id
func GetRequestID(ctx context.Context) string {
ctx.Value(requestIDKey).(string)
}
func Handle(rw http.ResponseWriter, req *http.Request) {
// 拿到 reqId,后面可以记录日志等等
reqID := GetRequestID(req.Context())
...
}
func main() {
handler := WithRequestID(http.HandlerFunc(Handle))
http.ListenAndServe("/", handler)
}
取消 goroutine #
我们先来设想一个场景:打开外卖的订单页,地图上显示外卖小哥的位置,而且是每秒更新 1 次。app 端向后台发起 websocket 连接(现实中可能是轮询)请求后,后台启动一个协程,每隔 1 秒计算 1 次小哥的位置,并发送给端。如果用户退出此页面,则后台需要“取消”此过程,退出 goroutine,系统回收资源。
后端可能的实现如下:
|
|
如果需要实现“取消”功能,并且在不了解 context 功能的前提下,可能会这样做:给函数增加一个指针型的 bool 变量,在 for 语句的开始处判断 bool 变量是发由 true 变为 false,如果改变,则退出循环。
上面给出的简单做法,可以实现想要的效果,没有问题,但是并不优雅,并且一旦协程数量多了之后,并且各种嵌套,就会很麻烦。优雅的做法,自然就要用到 context。
|
|
主流程可能是这样的:
|
|
注意一个细节,WithTimeOut 函数返回的 context 和 cancelFun 是分开的。context 本身并没有取消函数,这样做的原因是取消函数只能由外层函数调用,防止子节点 context 调用取消函数,从而严格控制信息的流向:由父节点 context 流向子节点 context。
防止 goroutine 泄漏 #
前面那个例子里,goroutine 还是会自己执行完,最后返回,只不过会多浪费一些系统资源。这里改编一个“如果不用 context 取消,goroutine 就会泄漏的例子”,
|
|
这是一个可以生成无限整数的协程,但如果我只需要它产生的前 5 个数,那么就会发生 goroutine 泄漏:
|
|
当 n == 5 的时候,直接 break 掉。那么 gen 函数的协程就会执行无限循环,永远不会停下来。发生了 goroutine 泄漏。
用 context 改进这个例子:
|
|
增加一个 context,在 break 前调用 cancel 函数,取消 goroutine。gen 函数在接收到取消信号后,直接退出,系统回收资源。
golang之context的更多相关文章
- golang中Context的使用场景
golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...
- Golang的Context介绍及其源码分析
简介 在Go服务中,对于每个请求,都会起一个协程去处理.在处理协程中,也会起很多协程去访问资源,比如数据库,比如RPC,这些协程还需要访问请求维度的一些信息比如说请求方的身份,授权信息等等.当一个请求 ...
- 深入理解golang:Context
一.背景 在golang中,最主要的一个概念就是并发协程 goroutine,它只需用一个关键字 go 就可以开起一个协程,并运行. 一个单独的 goroutine运行,倒也没什么问题.如果是一个go ...
- Golang之Context的使用
转载自:http://www.nljb.net/default/Golang%E4%B9%8BContext%E7%9A%84%E4%BD%BF%E7%94%A8/ 简介 在golang中的创建一个新 ...
- golang 之 context包
概述 context是Go中广泛使用的程序包,由Google官方开发,在1.7版本引入.它用来简化在多个go routine传递上下文数据.(手动/超时)中止routine树等操作,比如,官方 ...
- golang中context包学习
摘要 go语言中goroutine之间的关联关系,缺乏维护,在erlang中有专门的机制来保障新开仟程的生命周期, 在go语言中,只能通过channel + select来实现,但不够直观,感觉很绕. ...
- golang从context源码领悟接口的设计
注:写帖子时go的版本是1.12.7 go语言中实现一个interface不用像其他语言一样需要显示的声明实现接口.go语言只要实现了某interface的方法就可以做类型转换.go语言没有继承的概念 ...
- 【GoLang】golang context channel 详解
代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...
- golang中的context包
标准库的context包 从设计角度上来讲, golang的context包提供了一种父routine对子routine的管理功能. 我的这种理解虽然和网上各种文章中讲的不太一样, 但我认为基本上还是 ...
- Golang Context 详细介绍
Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果 ...
随机推荐
- ROS 知识
安装 Ref: ROS安装过程中如何解决 rosdep update 命令出现错误 https://jiayaoo3o.github.io/2020/06/23/%E8%AE%B0%E5%BD%95% ...
- Unity 中 Color 与 Color32 的区别
1. 存储方式 Color用四个浮点数(float)来表示RGBA,取值范围均是0到1 举例: var orange = new Color(1f, 0.5f, 0f, 1f); 而Color32使用 ...
- SNERT内部集训-WEB
SNERT内部集训-WEB Day1-2 文件上传 靶场搭建 docker安装,docker pull c0ny1/upload-labs,docker run -it -d -p 8080:80 a ...
- JavaScript – Decimal
前言 之前就写过一篇 decimal, double, float, 但有点杂乱, 这篇把 JS 的部分独立写成一篇整理版. 参考 JavaScript 浮点数运算的精度问题 关于JavaScript ...
- Flutter 不容错过的 7 大亮点 | Google I/O 精彩回顾
Flutter 在今年的 Google I/O 上发布了许多重磅更新,欢迎大家和我们一起了解其中不容错过的 7 大亮点, 点击这里 观看 Flutter 不容错过的 7 大亮点 视频了解更多信息. F ...
- JavaScript 学习路线图
基础阶段 主要内容: 掌握 JavaScript 的基本语法,如变量.数据类型(字符串.数字.布尔.对象.数组等).运算符等. 理解程序的控制流,包括条件语句(如 if-else).循环语句(如 fo ...
- Linux 进程调度之schdule主调度器
考虑到文章篇幅,在这里我只讨论普通进程,其调度算法采用的是CFS(完全公平)调度算法. 至于CFS调度算法的实现后面后专门写一篇文章,这里只要记住调度时选择一个优先级最高的任务执行 一.调度单位简介 ...
- nestjs 登录和验证码结合验证 svgCaptcha 包 session 会话标识
// ps: 现在用户验证使用 token jwt 了 代替了 session // session 是服务器为每个用户建立的唯一标识 以区分用户 会话标识 // session 是express中的 ...
- iotdb时序数据库常见使用命令
docker 安装IOTDB核心代码: #docker启动 docker run -d -p 6667:6667 -p 31999:31999 -p 8181:8181 --name some-iot ...
- 02 Transformer 中 Add&Norm (残差和标准化)代码实现
python/pytorch 基础 https://www.cnblogs.com/nickchen121 培训机构(Django 类似于 Transformers) 首先由一个 norm 函数 no ...