概述
介绍
context 是整个 Go 生态系统的关键部分。在这篇简短的文章中,我将解释 context 在 Go 应用程序中是如何工作的。
概述
从非常高级的角度来看,可以将 Context 与n叉树数据结构进行比较。查看下图:
在本文中,我将只考虑最有意思的 Go context WithCancel。
带有取消的 context
当向 context 上下文发出取消信号时,所有监听上下文的goroutine都会被中断。如果父上下文发出了取消信号,那么从父上下文派生的所有子上下文也收到取消信号,因此所有监听上下文(父上下文和子上下文)的 goroutine 都会被中断。
好的。让我们查看使用场景。
场景 1
上图中发生了什么?
Goroutines 1,2和3使用相同的 context。
Goroutines 2 & 3 正在使用<-ctx.Done通道监听上下文中的任何取消信号。
Goroutine 1 发出了取消信号,goroutines 2 和 3 立即被中断。
场景 2
现在,让我们检查一个不同的场景。
Goroutine 1 在与 goroutines 2 & 3 不同的环境中工作。
goroutines 2 & 3 的上下文不是从goroutine 1的上下文派生的。
现在,如果 goroutine 1 发出取消事件,goroutines 2 和 3 会被中断吗?
答案是否定的。
场景 3
Goroutine 1 在与 goroutines 2 & 3 不同的环境中工作。
goroutines 2 & 3 的上下文是从 goroutine 1 的上下文派生的。
现在,如果 goroutine 1 发出取消事件,goroutines 2 和 3 会被中断吗?
答案是肯定的。
场景 4
如果为根上下文发出取消事件会发生什么?
那么,在这种情况下,所有的 goroutine 都会被中断。
看一些代码
下面是我在图形中的代码逻辑。
我有两个阶段,第一阶段和第二阶段。
我从根上下文派生的阶段 1 创建了ctxCancelStage1 。
现在,我为阶段 2 创建了另一个从ctxCancelStage1派生的上下文ctxCancelStage2 。
第 1 阶段正在生成有关通道数据的一些数据,第 2 阶段正在从同一通道读取数据。
阶段 1 正在为ctxCancelStage1发出取消信号,因此,阶段 2中的 goroutine (worker)正在监听事件并进行相应的处理。在演示代码中,它只是退出循环以停止处理数据。
package main
import (
"context"
"fmt"
"sync"
)
func main() {
InitComplexContext()
}
func InitComplexContext() {
wg := sync.WaitGroup{}
ctxRoot := context.Background()
data := make(chan int)
// Stage 1
ctxCancelStage1, cancel := context.WithCancel(ctxRoot)
a1(data, cancel, ctxCancelStage1, &wg)
// Stage 2
ctxCancelStage2, _ := context.WithCancel(ctxCancelStage1)
a2(data, ctxCancelStage2, &wg)
wg.Wait()
}
func a1(data chan int, cancel context.CancelFunc, ctxCancel context.Context, wg *sync.WaitGroup) {
worker := func(workerId int, data chan int, cancel context.CancelFunc, ctxCancel context.Context, wg *sync.WaitGroup) {
defer wg.Done()
flag := true
// Data block.
d := 0
for flag {
if d == 5 {
flag = false
cancel()
} else {
d = d + 1
fmt.Printf(">>> A1, Worker:%d, Sent:%dn", workerId, d)
data <- d
}
}
}
// Invoke workers.
wg.Add(1)
go worker(1, data, cancel, ctxCancel, wg)
}
func a2(data chan int, ctxCancel context.Context, wg *sync.WaitGroup) {
worker := func(workerId int, data chan int, ctxCancel context.Context, wg *sync.WaitGroup) {
defer wg.Done()
// Data block.
flag := true
for flag {
select {
case <-ctxCancel.Done():
fmt.Println("### A2 exited.")
flag = false
case d := <-data:
fmt.Printf("<<< A2, Worker:%d, Received:%dn", workerId, d)
default:
}
}
}
wg.Add(1)
go worker(1, data, ctxCancel, wg)
}
在执行代码时,您应该会看到以下输出。
现在,让我们稍微调整一下代码。
在这里,我创建了从根上下文派生的ctxCancelStage2。第 2 阶段正在处理这种情况。
在ctxCancelStage1上触发了取消事件。
第二阶段会被打断吗?
答案是否定的。在演示代码中,阶段 2中的worker继续无限循环。(内存泄漏)。
下面是结果。
结论
在这篇简短的文章中,我解释了 Go 中 Context 的用例。我发现它在处理并发请求时非常有用。希望这篇文章能帮助我们弄清楚 Go context 上下文是如何工作的。
推荐
我在使用 Go 过程中犯过的低级错误
深入理解 goroutine 泄漏和避免泄漏的最佳实践
随手关注或者”在看“,诚挚感谢!
最后
以上就是醉熏唇膏为你收集整理的图解 Golang Context的全部内容,希望文章能够帮你解决图解 Golang Context所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复