当前位置: 首页 > article >正文

golang中的上下文

背景

在Go语言中,使用context包来管理跨API和进程间的请求生命周期是常见的做法。特别是在涉及到并发编程时,如启动协程(goroutine)来处理异步任务,正确地传递和监听context变得尤为重要。比如,在gin框架中,发起一个http请求,把context传递到协程中,当http请求结束时就会执行上下文取消逻辑,从而导致异步协程退出。

我们通过一个示例模拟在gin框架(或任何类似的Web框架)中可能遇到的一个问题:在HTTP请求处理过程中启动协程,并传递context以控制协程的生命周期。

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// 创建一个带取消功能的上下文
	ctx, cancel := context.WithCancel(context.Background())

	// 启动一个协程
	go func(ctx context.Context) {
		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop() // 确保ticker在退出时停止

		for {
			select {
			case <-ticker.C:
				fmt.Println("协程正在运行...")
			case <-ctx.Done():
				fmt.Println("协程收到取消信号,退出执行")
				return
			}
		}
	}(ctx)

	// 主程序等待3秒后取消上下文
	time.Sleep(3 * time.Second)
	cancel()

	time.Sleep(1 * time.Second)
	fmt.Println("主程序结束")
}

当我们执行取消操作,异步协程监听到Done信号,执行了退出协程的逻辑。 

但是,在某些情况下,比如,上述提到的http请求这样的情况下,我们并不期望context取消,导致异步协程退出执行。

那我们应该怎么办呢?

很容易想到的是new一个新的context使其继承父context。但是,这里就会遇到一个问题,简单的拷贝父context是不行的,比如,下面这种写法,当父ctx取消了,newCtx也会取消。

ctx, cancel := context.WithCancel(context.Background())
	newCtx, _ := context.WithTimeout(ctx, 3*time.Second)

那么要怎么办呢?context包中有一个方法:WithoutCancel,这个方法只拷贝了父ctx的Value,没有继承父ctx的Done通道。

案例

我们提供一个简单的案例,模拟在协程中处理逻辑。

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// 创建一个带取消功能的上下文
	ctx, cancel := context.WithCancel(context.Background())
	ctx = context.WithValue(ctx, "traceId", "123456")
	ctx = context.WithValue(ctx, "userId", "u123")

	newCtx := context.WithoutCancel(ctx)
	// 启动一个协程
	go func(ctx context.Context) {
		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop() // 确保ticker在退出时停止

		for {
			select {
			case <-ticker.C:
				fmt.Println("traceId:", ctx.Value("traceId"))
				fmt.Println("userId:", ctx.Value("userId"))
				fmt.Println("协程正在运行...")
			case <-ctx.Done():
				fmt.Println("协程收到取消信号,退出执行")
				return
			}
		}
	}(newCtx)

	// 主程序等待3秒后取消上下文
	time.Sleep(3 * time.Second)
	cancel()

	<-newCtx.Done()
	// 这种情况下,不会收到取消信号,父上下文的取消信号不会传递给子上下文
	// 给协程一些时间进行清理和输出
	time.Sleep(1 * time.Second)
	fmt.Println("主程序结束")
}

在Go语言的context包中,WithoutCancel函数提供了一种创建新上下文的方法,这个新上下文不会继承其父上下文的取消逻辑。这意味着,当父上下文被取消时,由WithoutCancel返回的上下文并不会被自动取消。

// WithoutCancel 返回一个父上下文的副本,该副本在父上下文被取消时不会被取消。  
// 返回的上下文没有截止时间或错误,并且其 Done 通道为 nil。  
// 对返回的上下文调用 [Cause] 方法会返回 nil。  
func WithoutCancel(parent Context) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	return withoutCancelCtx{parent}
}

type withoutCancelCtx struct {
	c Context
}

// withoutCancelCtx 类型的 Done 方法被重写,返回 nil 通道。  
func (w withoutCancelCtx) Done() <-chan struct{} {  
    return nil // 返回空的 Done 通道,表示这个上下文不会被取消  
}

WithoutCancel函数接受一个父上下文作为参数,并返回一个新的上下文。这个新上下文是一个withoutCancelCtx类型的实例,它内部包含了父上下文,但重写了Done方法。

由于withoutCancelCtx重写了Done方法并返回nil,这意味着当你监听这个上下文的Done通道时,它永远不会关闭,因此你的协程或任务将不会因为父上下文的取消而退出。这对于需要确保某些任务在父上下文取消后仍然继续运行的场景非常有用。


http://www.kler.cn/news/362291.html

相关文章:

  • 复旦大学全球供应链研究中心揭牌,合合信息共话大数据赋能
  • 【经管】比特币与以太坊历史价格数据集(2014.1-2024.5)
  • Python 第七节 魔法圆阵
  • java高频面试题汇总
  • 全光网络架构
  • ELK中segemntmerge操作对写入性能的影响以及控制策略和优化方法
  • 滚雪球学Redis[7.4讲]:Redis在分布式系统中的应用:微服务与跨数据中心策略
  • 016_基于python+django网络爬虫及数据分析可视化系统2024_kyz52ks2
  • Python 应用可观测重磅上线:解决 LLM 应用落地的“最后一公里”问题
  • python如何基于numpy pandas完成复杂的数据分析操作?
  • 华企盾对当前网络安全挑战与应对策略探讨
  • LeetCode102. 二叉树的层序遍历(2024秋季每日一题 43)
  • 毕业设计项目系统:基于Springboot框架的心理咨询评估管理系统,完整源代码+数据库+毕设文档+部署说明
  • python将1格式化为01
  • 思科网络设备命令
  • 9个用于测试自动化的最佳AI测试工具(2024)
  • NoSQL数据库分类简述
  • DSVPN简介与应用
  • Stable Diffusion Web UI 大白话术语解释 (二)
  • 中小型医院网站:Spring Boot开发技巧
  • 【Jmeter】jmeter指定jdk版本启动
  • 利用grid sample优化BevDet
  • ACM CCS 2024现场直击:引爆通信安全新纪元
  • 通过conda install -c nvidia cuda=“11.3.0“ 安装低版本的cuda,但是却安装了高版本的12.4.0
  • PHP 任务管理:跨行业的科技驱动力量
  • rabbitmq 工作队列模式