原文链接: Go 语言 context 都能做什么?

很多 Go 项目的源码,在读的过程中会发现一个很常见的参数 ctx,而且基本都是作为函数的第一个参数。

为什么要这么写呢?这个参数到底有什么用呢?带着这样的疑问,我研究了这个参数背后的故事。

开局一张图:

核心是 Context 接口:

  1. // A Context carries a deadline, cancelation signal, and request-scoped values
  2. // across API boundaries. Its methods are safe for simultaneous use by multiple
  3. // goroutines.
  4. type Context interface {
  5. // Done returns a channel that is closed when this Context is canceled
  6. // or times out.
  7. Done() <-chan struct{}
  8. // Err indicates why this context was canceled, after the Done channel
  9. // is closed.
  10. Err() error
  11. // Deadline returns the time when this Context will be canceled, if any.
  12. Deadline() (deadline time.Time, ok bool)
  13. // Value returns the value associated with key or nil if none.
  14. Value(key interface{}) interface{}
  15. }

包含四个方法:

  • Done():返回一个 channel,当 times out 或者调用 cancel 方法时。
  • Err():返回一个错误,表示取消 ctx 的原因。
  • Deadline():返回截止时间和一个 bool 值。
  • Value():返回 key 对应的值。

有四个结构体实现了这个接口,分别是:emptyCtx, cancelCtx, timerCtxvalueCtx

其中 emptyCtx 是空类型,暴露了两个方法:

  1. func Background() Context
  2. func TODO() Context

一般情况下,会使用 Background() 作为根 ctx,然后在其基础上再派生出子 ctx。要是不确定使用哪个 ctx,就使用 TODO()

另外三个也分别暴露了对应的方法:

  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, val interface{}) Context

遵循规则

在使用 Context 时,要遵循以下四点规则:

  1. 不要将 Context 放入结构体,而是应该作为第一个参数传入,命名为 ctx
  2. 即使函数允许,也不要传入 nil 的 Context。如果不知道用哪种 Context,可以使用 context.TODO()
  3. 使用 Context 的 Value 相关方法只应该用于在程序和接口中传递和请求相关的元数据,不要用它来传递一些可选的参数。
  4. 相同的 Context 可以传递给不同的 goroutine;Context 是并发安全的。

WithCancel

  1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

WithCancel 返回带有新 Done 通道的父级副本。当调用返回的 cancel 函数或关闭父上下文的 Done 通道时,返回的 ctxDone 通道将关闭。

取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用 cancel

举个例子:

这段代码演示了如何使用可取消上下文来防止 goroutine 泄漏。在函数结束时,由 gen 启动的 goroutine 将返回而不会泄漏。

  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. gen := func(ctx context.Context) <-chan int {
  13. dst := make(chan int)
  14. n := 1
  15. go func() {
  16. for {
  17. select {
  18. case <-ctx.Done():
  19. return // returning not to leak the goroutine
  20. case dst <- n:
  21. n++
  22. }
  23. }
  24. }()
  25. return dst
  26. }
  27. ctx, cancel := context.WithCancel(context.Background())
  28. defer cancel() // cancel when we are finished consuming integers
  29. for n := range gen(ctx) {
  30. fmt.Println(n)
  31. if n == 5 {
  32. break
  33. }
  34. }
  35. }

输出:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5

WithDeadline

  1. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

WithDeadline 返回父上下文的副本,并将截止日期调整为不晚于 d。如果父级的截止日期已经早于 d,则 WithDeadline(parent, d) 在语义上等同于 parent

当截止时间到期、调用返回的取消函数时或当父上下文的 Done 通道关闭时,返回的上下文的 Done 通道将关闭。

取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用取消。

举个例子:

这段代码传递具有截止时间的上下文,来告诉阻塞函数,它应该在到达截止时间时立刻退出。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. const shortDuration = 1 * time.Millisecond
  8. func main() {
  9. d := time.Now().Add(shortDuration)
  10. ctx, cancel := context.WithDeadline(context.Background(), d)
  11. // Even though ctx will be expired, it is good practice to call its
  12. // cancellation function in any case. Failure to do so may keep the
  13. // context and its parent alive longer than necessary.
  14. defer cancel()
  15. select {
  16. case <-time.After(1 * time.Second):
  17. fmt.Println("overslept")
  18. case <-ctx.Done():
  19. fmt.Println(ctx.Err())
  20. }
  21. }

输出:

  1. context deadline exceeded

WithTimeout

  1. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))

取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用取消。

举个例子:

这段代码传递带有超时的上下文,以告诉阻塞函数应在超时后退出。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. const shortDuration = 1 * time.Millisecond
  8. func main() {
  9. // Pass a context with a timeout to tell a blocking function that it
  10. // should abandon its work after the timeout elapses.
  11. ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
  12. defer cancel()
  13. select {
  14. case <-time.After(1 * time.Second):
  15. fmt.Println("overslept")
  16. case <-ctx.Done():
  17. fmt.Println(ctx.Err()) // prints "context deadline exceeded"
  18. }
  19. }

输出:

  1. context deadline exceeded

WithValue

  1. func WithValue(parent Context, key, val any) Context

WithValue 返回父级的副本,其中与 key 关联的值为 val

其中键必须是可比较的,并且不应是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该定义自己的键类型。

为了避免分配给 interface{},上下文键通常具有具体的 struct{} 类型。或者,导出的上下文键变量的静态类型应该是指针或接口。

举个例子:

这段代码演示了如何将值传递到上下文以及如何检索它(如果存在)。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. )
  6. func main() {
  7. type favContextKey string
  8. f := func(ctx context.Context, k favContextKey) {
  9. if v := ctx.Value(k); v != nil {
  10. fmt.Println("found value:", v)
  11. return
  12. }
  13. fmt.Println("key not found:", k)
  14. }
  15. k := favContextKey("language")
  16. ctx := context.WithValue(context.Background(), k, "Go")
  17. f(ctx, k)
  18. f(ctx, favContextKey("color"))
  19. }

输出:

  1. found value: Go
  2. key not found: color

本文的大部分内容,包括代码示例都是翻译自官方文档,代码都是经过验证可以执行的。如果有不是特别清晰的地方,可以直接去读官方文档。

以上就是本文的全部内容,如果觉得还不错的话欢迎点赞转发关注,感谢支持。


官方文档:

源码分析:

推荐阅读:

Go 语言 context 都能做什么?的更多相关文章

  1. Context都没弄明白,还怎么做Android开发?

    Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...

  2. Context都没弄明白,还怎么做Android开发

    转载:https://www.jianshu.com/p/94e0f9ab3f1d Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过 ...

  3. PHP语言学习之php做图片上传功能

    本文主要向大家介绍了PHP语言学习之php做图片上传功能,通过具体的内容向大家展示,希望对大家学习php语言有所帮助. 今天来做一个图片上传功能的插件,首先做一个html文件:text.php < ...

  4. [语法]C语言中二维数组做输入参数

    C语言中二维数组做输入参数时, 可以同时指定各维长度, 可以只指定第二维的长度, 不可以只指定第一维的长度, 不可以各维长度都不指定. 一句话总结:要指定至少指定第二维,都不指定是不行的. 具体栗子如 ...

  5. NodeJS什么都能做,为什么还要JAVA?

    这张图看起来简单而且很好理解,但没尝试过,会有很多疑问. SPA模式中,后端已供了所需的数据接口,view前端已经可以控制,为什么要多加NodeJS这一层? 多加一层,性能怎么样? 多加一层,前端的工 ...

  6. Java 程序员每天都在做什么?

    作为一名 在大.中.小微企业都待过 的 Java 开发者,今天和大家分享下自己在不同公司的工作日常和收获.包括一些个人积累的工作提升经验,以及一些 Java 学习的方法和资源. 先从我的第一份 Jav ...

  7. 微信小程序来了,小程序都能做些什么

    2017年的微信大动作就是微信小程序了,到底小程序都能做些什么?这是很多人关注的热点,小程序开发对企业又有什么帮助呢?下面让厦门微信小程序开发公司来为你就分析下.       微信小程序与APP的关系 ...

  8. 腾讯QQ会员技术团队:人人都可以做深度学习应用:入门篇(下)

    四.经典入门demo:识别手写数字(MNIST) 常规的编程入门有"Hello world"程序,而深度学习的入门程序则是MNIST,一个识别28*28像素的图片中的手写数字的程序 ...

  9. Go语言Context(设计及分析)

    context简单概述: Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,该请求上的所有goroutines ...

  10. 【腾讯Bugly干货分享】人人都可以做深度学习应用:入门篇

    导语 2016年,继虚拟现实(VR)之后,人工智能(AI)的概念全面进入大众的视野.谷歌,微软,IBM等科技巨头纷纷重点布局,AI 貌似将成为互联网的下一个风口. 很多开发同学,对人工智能非常感兴趣, ...

随机推荐

  1. Java Stream流

    Java8 Stream流编程 Stream 使用一种类似于SQL语句从数据库查询数据的直观方式来提供对 Java 集合运算和表达的高阶抽象.得益于 Lambda 所带来的函数式编程,Stream A ...

  2. 从0到1手把手教你ASP.NET Core Web API项目配置接口文档Swagger(一)

    一.创建ASP.NET Core Web API项目(若项目已创建,则可跳过本节内容) 1.双击打开VS2022. 2.单击"创建新项目",如下图. 3.选择"ASP.N ...

  3. Rust中的into函数和from函数

    1.Rust中的into函数和from函数是做什么用的? into函数是Rust语言中的一个转换函数,它属于Into trait.它可以将一个类型转换为另一个类型.实现了From trait的类型会自 ...

  4. ros-python学习样例笔记

    1.通信基本原理介绍 待写 2.三种通信方式的程序样例(python版) 2.1 topic 通信方式(非自定义和自定义) 2.1.1 创建工作空间和topic功能包 在ubuntu中打开命令行,输入 ...

  5. gs_probackup增量备份ptrack.cpp : 88

    问题描述:使用gs_probackup对opengauss进行增量备份失败[omm@testmysqldb04 ~]$ sh gs_probackup.sh incbackup pg_switch_x ...

  6. xtrabackup8.0.27备份失败

    问题描述:mysql8.0.27备份出现中断,重新备份发现xtrabackup备份失败,xtrabackup与mysql版本不匹配,后来想起来时mysql进行了升级,8.0.27->8.0.29 ...

  7. defineProperty在数据劫持后是如何通知数据的更新和视图的更新的

    vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和ge ...

  8. count(*) count(1) count(字段)效率问题

    COUNT(字段名)和COUNT(*)的查询结果有什么不同? COUNT(1)和COUNT(*)之间的效率哪个更高? 你知道答案吗?很多人都认为COUNT(1)比COUNT(*)效率高,真的是这样吗? ...

  9. SpringBoot 集成 Quartz + MySQL

    Quartz 简单使用 Java SpringBoot 中,动态执行 bean 对象中的方法 源代码地址 => https://gitee.com/VipSoft/VipBoot/tree/de ...

  10. 28-PWA

    const { resolve } = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin') ...