go - context 用法

比眉伴天荒 2022-06-05 00:17 364阅读 0赞

1,context作用

1,通过context,我们可以方便地对同一个请求所产生地goroutine进行约束管理,可以设定超时、deadline,甚至是取消这个请求相关的所有goroutine。形象地说,假如一个请求过来,需要A去做事情,而A让B去做一些事情,B让C去做一些事情,A、B、C是三个有关联的goroutine,那么问题来了:假如在A、B、C还在处理事情的时候请求被取消了,那么该如何优雅地同时关闭goroutine A、B、C呢?这个时候就轮到context包上场了。

2,在golang中的创建一个新的线程并不会返回像c语言类似的pid所有我们不能从外部杀死某个线程,所有我就得让它自己结束之前我们用channel+select的方式,来解决这个问题但是有些场景实现起来比较麻烦,例如由一个请求衍生出多个线程并且之间需要满足一定的约束关系,以实现一些诸如:

有效期,中止线程树,传递请求全局变量之类的功能。

于是google 就为我们提供一个解决方案,开源了context包。使用context包来实现上下文功能 …..
约定:需要在你的方法的传入参数的第一个参数是context.Context的变量。

2,context结构

使用例子(1):

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. )
  6. func main() {
  7. // gen generates integers in a separate goroutine and
  8. // sends them to the returned channel.
  9. // The callers of gen need to cancel the context once
  10. // they are done consuming generated integers not to leak
  11. // the internal goroutine started by gen.
  12. // 定义 goroutine 函数
  13. gen := func(ctx context.Context) <-chan int {
  14. dst := make(chan int)
  15. n := 1
  16. go func() {
  17. for {
  18. select {
  19. case <-ctx.Done():
  20. return // returning not to leak the goroutine
  21. case dst <- n:
  22. n++
  23. }
  24. }
  25. }()
  26. return dst
  27. }
  28. ctx, cancel := context.WithCancel(context.Background())
  29. defer cancel() // main 方法执行完后,结束 ctx 相关的 goroutine
  30. for n := range gen(ctx) {
  31. fmt.Println(n)
  32. if n == 5 {
  33. break
  34. }
  35. }
  36. }

使用例子(2):

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "golang.org/x/net/context"
  6. )
  7. func Cdd(ctx context.Context) int {
  8. fmt.Println(ctx.Value("NLJB"))
  9. select {
  10. // 结束时候做点什么 ...
  11. case <-ctx.Done():
  12. return -3
  13. default:
  14. // 没有结束 ... 执行 ...
  15. }
  16. }
  17. func Bdd(ctx context.Context) int {
  18. fmt.Println(ctx.Value("HELLO"))
  19. fmt.Println(ctx.Value("WROLD"))
  20. ctx = context.WithValue(ctx, "NLJB", "NULIJIABEI")
  21. go fmt.Println(Cdd(ctx))
  22. select {
  23. // 结束时候做点什么 ...
  24. case <-ctx.Done():
  25. return -2
  26. default:
  27. // 没有结束 ... 执行 ...
  28. }
  29. }
  30. func Add(ctx context.Context) int {
  31. ctx = context.WithValue(ctx, "HELLO", "WROLD")
  32. ctx = context.WithValue(ctx, "WROLD", "HELLO")
  33. go fmt.Println(Bdd(ctx))
  34. select {
  35. // 结束时候做点什么 ...
  36. case <-ctx.Done():
  37. return -1
  38. default:
  39. // 没有结束 ... 执行 ...
  40. }
  41. }
  42. func main() {
  43. // 自动取消(定时取消)
  44. {
  45. timeout := 3 * time.Second
  46. ctx, _ := context.WithTimeout(context.Background(), timeout)
  47. fmt.Println(Add(ctx))
  48. }
  49. // 手动取消
  50. // {
  51. // ctx, cancel := context.WithCancel(context.Background())
  52. // go func() {
  53. // time.Sleep(2 * time.Second)
  54. // cancel() // 在调用处主动取消
  55. // }()
  56. // fmt.Println(Add(ctx))
  57. // }
  58. select {}
  59. }

(1)context.Context 接口

  • context包里的方法是线程安全的,可以被多个线程使用
  • 当Context被canceled或是timeout, Done返回一个被closed 的channel
  • 在Done的channel被closed后, Err代表被关闭的原因
  • 如果存在, Deadline 返回Context将要关闭的时间
  • 如果存在,Value 返回与 key 相关了的值,不存在返回 nil

    // context 包的核心
    type Context interface {

    1. Done() <-chan struct{}
    2. Err() error
    3. Deadline() (deadline time.Time, ok bool)
    4. Value(key interface{ }) interface{ }

    }

  • Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

  • Done方法返回一个信道(channel)。当Context被cancenl或timeout时,该信道被关闭。它是一个表示Context是否已关闭的信号。
  • 当Done信道关闭后,Err方法表明Context被撤的原因。
  • Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

(2)接口的实现

我们不需要手动实现这个接口,context 包已经给我们提供了两个

  • Background
  • TODO

这两个函数都会返回一个Context的实例只是返回的这两个实例都是空 Context。

  1. /* TODO返回一个非nil,empty的上下文 在目前还不清楚要使用的上下文或尚不可用时 */
  2. context.TODO()
  3. /* Background返回一个非nil,empty的上下文。 这是没有取消,没有值,并且没有期限。 它通常用于由主功能,初始化和测试,并作为输入的顶层上下文 */
  4. context.Background()

3,context包里的方法

(1)方法的使用

Context接口没有提供方法来设置其值和过期时间,也没有提供方法直接将其自身撤销。也就是说,Context不能改变和撤销其自身。那么该怎么通过Context传递改变后的状态呢?

context包里,主要使用的方法:

  1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
  2. func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
  3. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
  4. func WithValue(parent Context, key interface{}, val interface{}) Context
  • WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出
  • WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))
  • WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值

Context 的调用应该是链式的,通过WithCancel,WithDeadline,WithTimeout或WithValue派生出新的 Context。当父 Context 被取消时,其派生的所有 Context 都将取消。

通过context.WithXXX都将返回新的 Context 和 CancelFunc。调用 CancelFunc 将取消子代,移除父代对子代的引用,并且停止所有定时器。未能调用 CancelFunc 将泄漏子代,直到父代被取消或定时器触发。go vet工具检查所有流程控制路径上使用 CancelFuncs。

(2)context的创建

所有的context的父对象,也叫根对象,是一个空的context,它不能被取消,它没有值,从不会被取消,也没有超时时间,它常常作为处理request的顶层context存在,然后通过WithCancel、WithTimeout函数来创建子对象来获得cancel、timeout的能力

当顶层的request请求函数结束后,我们就可以cancel掉某个context,从而通知别的routine结束

其它方法例子

请参考:快速掌握 Golang context 包,简单示例

参考

快速掌握 Golang context 包,简单示例
理解Go Context机制
Golang之Context的使用
golang context初探

未读文章

How to correctly use context.Context in Go 1.7
Context API explained
Go Concurrency Patterns: Context

发表评论

表情:
评论列表 (有 0 条评论,364人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Go——习惯

    1、干净与强迫症 Go在代码干净上有了近乎苛刻的要求,主要体现在如下几个方面: 1. 编译器不能通过未使用的局部变量(包括未使用的标签)。 2. “import”未

    相关 go语言Switch

    go语言中除了两个基础的控制语句if和for,还有switch语句,它是多路分支控制,下面给出一个示例进行说明,主要通过命令行传入一个参数,然后跟据其值情况分别返回不同的值。如

    相关 go - context

    1,context作用 1,通过context,我们可以方便地对同一个请求所产生地goroutine进行约束管理,可以设定超时、deadline,甚至是取消这个请求相关的