图解 Golang Context

本文介绍了Go生态系统中的关键组件——Context。通过四个场景展示了Context如何在goroutines间传递取消信号,特别是在并发处理中。文章通过代码示例说明了当Context被取消时,如何影响依赖它的goroutines。总结了Context在处理并发请求时的重要性。
摘要由CSDN通过智能技术生成

介绍

context 是整个 Go 生态系统的关键部分。在这篇简短的文章中,我将解释 context 在 Go 应用程序中是如何工作的。

概述

从非常高级的角度来看,可以将 Context 与n叉树数据结构进行比较。查看下图:

dc2cd16c77e01fc2716657a29b6ef578.png

在本文中,我将只考虑最有意思的 Go context WithCancel。

带有取消的 context

当向 context 上下文发出取消信号时,所有监听上下文的goroutine都会被中断。如果父上下文发出了取消信号,那么从父上下文派生的所有子上下文也收到取消信号,因此所有监听上下文(父上下文和子上下文)的 goroutine 都会被中断。

好的。让我们查看使用场景。

场景 1

ada3b947b7cd96f6c3fae8246480059c.png

上图中发生了什么?

  • Goroutines 1,2和3使用相同的 context。

  • Goroutines 2 & 3 正在使用<-ctx.Done通道监听上下文中的任何取消信号。

  • Goroutine 1 发出了取消信号,goroutines 2 和 3 立即被中断。

场景 2

现在,让我们检查一个不同的场景。

7ca39c20305d7b415d082d3c020e5056.png

  • Goroutine 1 在与 goroutines 2 & 3 不同的环境中工作。

  • goroutines 2 & 3 的上下文不是从goroutine 1的上下文派生的。

  • 现在,如果 goroutine 1 发出取消事件,goroutines 2 和 3 会被中断吗?

答案是否定的。

场景 3

6b3310c043ed52df508cba53e4d82355.png

  • Goroutine 1 在与 goroutines 2 & 3 不同的环境中工作。

  • goroutines 2 & 3 的上下文是从 goroutine 1 的上下文派生的。

  • 现在,如果 goroutine 1 发出取消事件,goroutines 2 和 3 会被中断吗?

答案是肯定的。

场景 4

0ece2655409eb97b3821a04e112ebf2b.png

  • 如果为根上下文发出取消事件会发生什么?

那么,在这种情况下,所有的 goroutine 都会被中断。

看一些代码

下面是我在图形中的代码逻辑。

58af988f2b975b1a94b76174691ca173.png

我有两个阶段,第一阶段和第二阶段。

我从根上下文派生的阶段 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:%d\n", 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:%d\n", workerId, d)
   default:

   }
  }
 }

 wg.Add(1)
 go worker(1, data, ctxCancel, wg)
}

在执行代码时,您应该会看到以下输出。

bae49dd85fcf03bbd2dd7c90dda9a688.png

现在,让我们稍微调整一下代码。

7e7c738a8be8ff14897626514a51b9cb.png

在这里,我创建了从根上下文派生的ctxCancelStage2。第 2 阶段正在处理这种情况。

在ctxCancelStage1上触发了取消事件。

第二阶段会被打断吗?

答案是否定的。在演示代码中,阶段 2中的worker继续无限循环。(内存泄漏)。

下面是结果。

2df7d1579e8824ed13294d8c07945b3f.png

结论

在这篇简短的文章中,我解释了 Go 中 Context 的用例。我发现它在处理并发请求时非常有用。希望这篇文章能帮助我们弄清楚 Go context 上下文是如何工作的。

2c3d385d4f994356be05d111cc6127ef.png

推荐

我在使用 Go 过程中犯过的低级错误

深入理解 goroutine 泄漏和避免泄漏的最佳实践


随手关注或者”在看“,诚挚感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值