Go语言并发编程基础上下文概念详解
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《Go语言并发编程基础上下文概念详解》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
在开发过程中,也有这个上下文(Context)的概念,而且上下文也必不可少,缺少上下文,就不能获取完整的程序信息。那么什么是程序中的上下文呢?
简单来说,就是在 API 之间或者函数调用之间,除了业务参数信息之外的额外信息。比如,服务器接收到客户端的 HTTP 请求之后,可以把客户端的 IP 地址和端口、客户端的身份信息、请求接收的时间、Trace ID 等信息放入到上下文中,这个上下文可以在后端的方法调用中传递。
1 Go 中的 Context
Golang 的上下文也是应用开发常用的并发控制工具。同理,上下文可以用于在程序中的 API 层或进程之间共享请求范围的数据,除此之外,Go 的 Context 库还提供取消信号(Cancel)以及超时机制(Timeout)。
Context 又被称为上下文,与 WaitGroup 不同的是,Context 对于派生 goroutine 有更强的控制力,可以管理多级的 goroutine。
但我们在 Go 中创建一个 goroutine 时,如果发生了一个错误,并且这个错误永远不会终止,而其他程序会继续进行。加入有一个不被调用的 goroutine 运行无限循环,如下所示:
package main
import "fmt"
func main() {
dataCom := []string{"alex", "kyrie", "kobe"}
go func(data []string) {
// 模拟大量运算的死循环
}(dataCom)
// 其他代码正常执行
fmt.Println("剩下的代码执行正常逻辑")
}
上面的例子并不完整,dataCom goroutine 可能会也可能不会成功处理数据。它可能会进入无限循环或导致错误。我们的其余代码将不知道发生了什么。
有多种方法可以解决这个问题。其中之一是使用通道向我们的主线程发送一个信号,表明这个 goroutine 花费的时间太长,应该取消它。
package main
import (
"fmt"
"time"
)
func main() {
stopChannel := make(chan bool)
dataCom := []string{"alex", "kyrie", "kobe"}
go func(stopChannel chan bool) {
go func(data []string) {
// 大量的计算
}(dataCom)
for range time.After(2 * time.Second) {
fmt.Println("此操作运行时间过长,取消中")
stopChannel <- true
}
}(stopChannel)
<-stopChannel
// 其他代码正常执行
fmt.Println("剩下的代码执行正常逻辑")
}
上面的逻辑很简单。我们正在使用一个通道向我们的主线程发出这个 goroutine 花费的时间太长的信号。但是同样的事情可以用 context 来完成,这正是 context 包存在的原因。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
dataCom := []string{"alex", "kyrie", "kobe"}
go func() {
go func(data []string) {
// 大量的计算
}(dataCom)
for range time.After(2 * time.Second) {
fmt.Println("此操作运行时间过长,取消中")
cancel()
return
}
}()
select {
case
<h2>2 Context 接口</h2>
<p><strong>Context</strong> 接口定义:</p>
<pre class="brush:go;">type Context interface {
Deadline() (deadline time.Time, ok bool)
Done <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context 接口定义了 4 个方法:
Deadline(): 返回取消此上下文的时间 deadline(如果有)。如果未设置 deadline 时,则返回 ok==false,此时 deadline 为一个初始值的 time.Time 值。后续每次调用这个对象的 Deadline 方法时,都会返回和第一次调用相同的结果。Done(): 返回一个用于探测 Context 是否取消的 channel,当 Context 取消会自动将该 channel 关闭,如果该 Context 不能被永久取消,该函数返回 nil。例如context.Background();如果Done被 close,Err 方法会返回 Done 被 close 的原因。Err(): 该方法会返回 context 被关闭的原因,关闭原因由 context 实现控制,不需要用户设置;如果Done()尚未关闭,则Err()返回 nilValue(): 在树状分布的goroutine之间共享数据,用 map 键值的工作方法,通过 key 值查询 value。
每次创建新上下文时,都会得到一个符合此接口的类型。上下文的真正实现隐藏在这个包和这个接口后面。这些是您可以创建的工厂类型的上下文:
context.TODO
context.Background
context.WithCancel
context.WithValue
context.WithTimeout
context.WithDeadline
3 Context Tree
在实际实现中,我们通常使用派生上下文。我们创建一个父上下文并将其传递到一个层,我们派生一个新的上下文,它添加一些额外的信息并将其再次传递到下一层,依此类推。通过这种方式,我们创建了一个从作为父级的根上下文开始的上下文树。
这种结构的优点是我们可以一次性控制所有上下文的取消。如果根信号关闭了上下文,它将在所有派生的上下文中传播,这些上下文可用于终止所有进程,立即释放所有内容。这使得上下文成为并发编程中非常强大的工具。

4 创建上下文
4.1 上下文创建函数
我们可以从现有的上下文中创建或派生上下文。顶层(根)上下文是使用 Background 或 TODO 方法创建的,而派生上下文是使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 方法创建的。
所有派生的上下文方法都返回一个取消函数 CancelFunc,但 WithValue 除外,因为它与取消无关。调用 CancelFunc 会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。调用 CancelFunc 失败会泄漏子项及其子项,直到父项被取消或计时器触发。
- context.Background() ctx Context
此函数返回一个空上下文。这通常只应在主请求处理程序或顶级请求处理程序中使用。这可用于为主函数、初始化、测试以及后续层或其他 goroutine 派生上下文的时候。
ctx, cancel := context.Background()
- context.TODO() ctx Context
此函数返回一个非 nil 的、空的上下文。没有任何值、不会被 cancel,不会超时,也没有截止日期。但是,这也应该仅在您不确定要使用什么上下文或者该函数还不能用于接收上下文时,可以使用这个方法,并且将在将来需要添加时使用。
ctx, cancel := context.TODO()
- context.WithValue(parent Context, key, val interface{}) Context
这个函数接受一个上下文并返回一个派生的上下文,其中值 val 与 key 相关联,并与上下文一起经过上下文树。
WithValue 方法其实是创建了一个类型为 valueCtx 的上下文,它的类型定义如下:
type valueCtx struct {
Context
key, val interface{}
}
这意味着一旦你得到一个带有值的上下文,任何从它派生的上下文都会得到这个值。该值是不可变的,因此是线程安全的。
提供的键必须是可比较的,并且不应该是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该为键定义自己的类型。
为避免在分配给 interface{} 时进行分配,上下文键通常具有具体类型 struct{}。或者,导出的上下文键变量的静态类型应该是指针或接口。
package main
import (
"context"
"fmt"
)
type contextKey string
func main() {
var authToken contextKey = "auth_token"
ctx := context.WithValue(context.Background(), authToken, "Hello123456")
fmt.Println(ctx.Value(authToken))
}
运行该代码:
$ go run .
Hello123456
- func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
此函数接收父上下文并返回派生上下文,返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。在这个派生上下文中,添加了一个新的 Done channel,该 channel 在调用 cancel 函数或父上下文的 Done 通道关闭时关闭。
要记住的一件事是,我们永远不应该在不同的函数或层之间传递这个 cancel ,因为它可能会导致意想不到的结果。创建派生上下文的函数应该只调用取消函数。
下面是一个使用 Done 通道演示 goroutine 泄漏的示例:
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for char := range randomCharGenerator(ctx) {
generatedChar := string(char)
fmt.Printf("%v\n", generatedChar)
if generatedChar == "o" {
break
}
}
}
func randomCharGenerator(ctx context.Context)
<p>运行结果:</p>
<blockquote><p>$ go run . <br>a<br>m<br>q<br>c<br>l<br>t<br>o</p></blockquote>
- func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
此函数从其父级返回派生上下文,返回一个 parent 的副本。
当期限超过或调用取消函数时,该派生上下文将被取消。例如,您可以创建一个在未来某个时间自动取消的上下文,并将其传递给子函数。当该上下文由于截止日期用完而被取消时,所有获得该上下文的函数都会收到通知停止工作并返回。如果 parent 的截止日期已经早于 d,则上下文的 Done 通道已经关闭。
下面是我们正在读取一个大文件的示例,该文件的截止时间为当前时间 2 毫秒。我们将获得 2 毫秒的输出,然后将关闭上下文并退出程序。
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"time"
)
func main() {
// context with deadline after 2 millisecond
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Millisecond))
defer cancel()
lineRead := make(chan string)
var fileName = "sample-file.txt"
file, err := os.Open(fileName)
if err != nil {
log.Fatalf("failed opening file: %s", err)
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// goroutine to read file line by line and passing to channel to print
go func() {
for scanner.Scan() {
lineRead
- func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
这个函数类似于 context.WithDeadline。不同之处在于它将持续时间作为输入而不是时间对象。此函数返回一个派生上下文,如果调用取消函数或超过超时持续时间,该上下文将被取消。
WithTimeout 的实现是:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
// 当前时间+timeout就是deadline
return WithDeadline(parent, time.Now().Add(timeout))
}
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout)) 。
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"time"
)
func main() {
// context with deadline after 2 millisecond
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
defer cancel()
lineRead := make(chan string)
var fileName = "sample-file.txt"
file, err := os.Open(fileName)
if err != nil {
log.Fatalf("failed opening file: %s", err)
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// goroutine to read file line by line and passing to channel to print
go func() {
for scanner.Scan() {
lineRead
<p>如果父上下文的 Done 通道关闭,它最终将关闭所有派生的 Done 通道(所有后代),如:</p>
<pre class="brush:go;">package main
import (
"context"
"fmt"
"time"
)
func main() {
c := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c
<p>在这里,由于我们在创建其他派生上下文后立即关闭 ctx2,因此所有其他上下文也会立即关闭,随机打印 ctx3、ctx4 和 ctx5 关闭消息。 ctx5 是从 ctx4 派生的,由于 ctx2 关闭的级联效应,它正在关闭。尝试多次运行,您会看到不同的结果。</p>
<p>使用 Background 或 TODO 方法创建的上下文没有取消、值或截止日期。</p>
<pre class="brush:go;">package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
_, ok := ctx.Deadline()
if !ok {
fmt.Println("no dealine is set")
}
done := ctx.Done()
if done == nil {
fmt.Println("channel is nil")
}
}
4.2 Context 使用规范
- 不要将上下文存储在结构类型中;相反,将 Context 显式传递给需要它的每个函数。 Context 应该是第一个参数,通常命名为 ctx。
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
- 不要传递 nil 上下文,即使函数允许。如果不确定要使用哪个 Context,请传递
context.TODO或使用context.Background()创建一个空的上下文对象。 - 仅使用上下文传递请求范围的数据。不要传递应该使用函数参数传递的数据。
- 始终寻找 goroutine 泄漏并有效地使用上下文来避免这种情况。
- 如果父上下文的 Done 通道关闭,它最终将关闭所有派生的 Done 通道(所有后代)
- 上下文只是临时做函数之间的上下文传透,不能持久化上下文
- key 的类型不应该是字符串类型或者其它内建类型,否则容易在包之间使用 Context 时候产生冲突。使用 WithValue 时,key 的类型应该是自己定义的类型。
4.3 Context 使用场景
- 上下文信息传递 (request-scoped),比如处理 http 请求、在请求处理链路上传递信息;
- 控制子 goroutine 的运行;
- 超时控制的方法调用;
- 可以取消的方法调用。
5 总结
Context 是在 Go 中进行并发编程时最重要的工具之一。上下文的主要作用是在多个 Goroutine 或者模块之间同步取消信号或者截止日期,用于减少对资源的消耗和长时间占用,避免资源浪费。标准库中的 database/sql、os/exec、net、net/http 等包中都使用到了 Context。
参考链接:
Go Concurrency Patterns: Context
今天关于《Go语言并发编程基础上下文概念详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
Golang基于JWT与Casbin身份验证授权实例详解
- 上一篇
- Golang基于JWT与Casbin身份验证授权实例详解
- 下一篇
- Go语言异步API设计的扇入扇出模式详解
-
- Golang · Go教程 | 5小时前 | 格式化输出 printf fmt库 格式化动词 Stringer接口
- Golangfmt库用法与格式化技巧解析
- 140浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang配置Protobuf安装教程
- 147浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang中介者模式实现与通信解耦技巧
- 378浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Golang云原生微服务实战教程
- 310浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Golang迭代器与懒加载结合应用
- 110浏览 收藏
-
- Golang · Go教程 | 6小时前 | 性能优化 并发安全 Golangslicemap 预设容量 指针拷贝
- Golangslicemap优化技巧分享
- 412浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Golang代理模式与访问控制实现解析
- 423浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Golang事件管理模块实现教程
- 274浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3166次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3378次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3407次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4511次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3787次使用
-
- Go保证并发安全底层实现详解
- 2023-02-24 417浏览
-
- Go语言开发保证并发安全实例详解
- 2023-01-07 328浏览
-
- Golang 手写一个简单的并发任务 manager
- 2022-12-23 367浏览
-
- Go语言使用goroutine及通道实现并发详解
- 2023-01-02 221浏览
-
- GO中sync包自由控制并发示例详解
- 2023-01-07 391浏览

