ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

go并行编程4-context

2022-05-15 17:31:35  阅读:195  来源: 互联网

标签:编程 请求 goroutine 传递 context go 上下文 函数


context

在 Go 服务中,每个传入的请求都在其自己的goroutine 中处理。请求处理程序通常启动额外的 goroutine 来访问其他后端,如数据库和 RPC 服务。处理请求的 goroutine 通常需要访问特定于请求(request-specific context)的值,例如最终用户的身份、授权令牌和请求的截止日期(deadline)。

当一个请求被取消或超时时,处理该请求的所有 goroutine 都应该快速退出(fail fast),这样系统就可以回收它们正在使用的任何资源。

Go 1.7 引入一个 context 包,它使得跨 API 边界的请求范围元数据、取消信号和截止日期很容易传递给处理请求所涉及的所有 goroutine(显示传递)。

其他语言: Thread Local Storage(TLS),XXXContext

 

 

 

 

 

 如何将 context 集成到 API 中? 在将 context 集成到 API 中时,要记住的最重要的一点是,它的作用域是请求级别的。

例如,沿单个数据库查询存在是有意义的,但沿数据库对象存在则没有意义。 目前有两种方法可以将 context 对象集成到 API 中:

  1. 首参数传递 context 对象,比如,参考 net 包 Dialer.DialContext。此函数执行正常的 Dial 操作,但可以通过 context 对象取消函数调用。
  2. 在第一个 request 对象中携带一个可选的 context 对象。例如 net/http 库的 Request.WithContext,通过携带给定的 context 对象,返回一个新的 Request 对象。     

 

 

 

 

 

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

 

 对服务器的传入请求应该创建一个Context。

使用 context 的一个很好的心智模型是它应该在程序中流动,应该贯穿你的代码。这通常意味着您不希望将其存储在结构体之中。它从一个函数传递到另一个函数,并根据需要进行扩展。理想情况下,每个请求都会创建一个 context 对象,并在请求结束时过期。

不存储上下文的一个例外是,当您需要将它放入一个结构中时,该结构纯粹用作通过通道传递的消息。如下例所示。

 

 

context.WithValue

context.WithValue 内部基于 valueCtx 实现:

为了实现不断的 WithValue,构建新的 context,内部在查找 key 时候,使用递归方式不断从当前,从父节点寻找匹配的 key,直到 root context(Backgrond 和 TODO Value 函数会返回 nil)。

 

 

 

 在上下文中传递调试或跟踪数据是安全的

context.WithValue 方法允许上下文携带请求范围的数据。这些数据必须是安全的,以便多个 goroutine 同时使用。这里的数据,更多是面向请求的元数据,不应该作为函数的可选参数来使用(比如 context 里面挂了一个sql.Tx 对象,传递到 data 层使用),因为元数据相对函数参数更加是隐含的,面向请求的。而参数是更加显示的。

同一个 context 对象可以传递给在不同 goroutine 中运行的函数;上下文对于多个 goroutine 同时使用是安全的。对于值类型最容易犯错的地方,在于 context value 应该是 immutable 的,每次重新赋值应该是新的 context,即: context.WithValue(ctx, oldvalue) https://pkg.go.dev/google.golang.org/grpc/metadata Context.Value should inform, not control

 

 比如我们新建了一个基于 context.Background() 的 ctx1,携带了一个 map 的数据,map 中包含了 “k1”: “v1” 的一个键值对,ctx1 被两个 goroutine 同时使用作为函数签名传入,如果我们修改了 这个map,会导致另外进行读 context.Value 的 goroutine 和修改 map 的 goroutine,在 map 对象上产生 data race。因此我们要使用 copy-on-write 的思路,解决跨多个 goroutine 使用数据、修改数据的场景。

 

 从 ctx1 中获取 map1(可以理解为 v1 版本的 map 数据)。构建一个新的 map 对象 map2,复制所有 map1 数据,同时追加新的数据 “k2”: “v2” 键值对,使用 context.WithValue 创建新的 ctx2,ctx2 会传递到其他的 goroutine 中。这样各自读取的副本都是自己的数据,写行为追加的数据,在 ctx2 中也能完整读取到,同时也不会污染 ctx1 中的数据。

 

 当一个上下文被取消时,从它派生的所有上下文也被取消。

当一个 context 被取消时,从它派生的所有 context 也将被取消。WithCancel(ctx) 参数 ctx 认为是 parent ctx,在内部会进行一个传播关系链的关联。Done() 返回 一个 chan,当我们取消某个parent context, 实际上上会递归层层 cancel 掉自己的 child context 的 done chan 从而让整个调用链中所有监听 cancel 的 goroutine 退出。

 

 

如果要实现一个超时控制,通过上面的 context 的 parent/child 机制,其实我们只需要启动一个定时器,然后在超时的时候,直接将当前的 context 给 cancel 掉,就可以实现监听在当前和下层的额 context.Done() 的 goroutine 的退出。

 

 

 

 

 总结:

  1. 对服务器的传入请求应该创建一个Context。
  2. 向服务器发出的调用应该接受一个Context。
  3. 不要在结构类型中存储context;相反,显式地将Context传递给每个需要它的函数。
  4. 它们之间的函数调用链必须传播上下文。
  5. 使用WithCancel、WithDeadline、WithTimeout或WithValue替换上下文。
  6. 当一个上下文被取消时,从它派生的所有上下文也被取消。
  7. 相同的上下文可以传递给运行在不同goroutine中的函数;上下文对于多个goroutine同时使用是安全的。
  8. 不要传递空上下文,即使函数允许这样做。如果您不确定要使用哪个上下文,则传递一个TODO上下文。
  9. 上下文值只用于传递进程和api的请求范围内的数据,而不是传递可选参数给函数。
  10. 所有阻塞/长时间操作都应该是可取消的。
  11. 上下文。值模糊了程序的流。
  12. 上下文。价值应该告知,而不是控制。
  13. 尽量不要使用context.Value。

 

标签:编程,请求,goroutine,传递,context,go,上下文,函数
来源: https://www.cnblogs.com/sunlong88/p/16273756.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有