首页 > 其他分享 >golang context上下文值传递与控制

golang context上下文值传递与控制

时间:2023-02-03 18:47:04浏览次数:47  
标签:context ctx golang Done Context time 汉堡 上下文

1. Context详解

在 Go 语言中 context 包允许传递一个 “context” 到程序中。 Context 如超时或截止日期(deadline)或通道,来指示停止运行和返回。例如,如果正在执行一个 web 请求或运行一个系统命令,定义一个超时对生产级系统通常是个好主意。因为,如果依赖的API运行缓慢,不希望在系统上备份(back up)请求,因为它可能最终会增加负载并降低所有请求的执行效率。导致级联效应。这是超时或截止日期 context 派上用场的地方。

1.1. 设计原理

Go 语言中的每一个请求的都是通过一个单独的 Goroutine 进行处理的,HTTP/RPC 请求的处理器往往都会启动新的 Goroutine 访问数据库和 RPC 服务,我们可能会创建多个 Goroutine 来处理一次请求,而 Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、取消信号以及处理请求的截止日期。

每一个 Context 都会从最顶层的 Goroutine 一层一层传递到最下层,这也是 Golang 中上下文最常见的使用方式,如果没有 Context,当上层执行的操作出现错误时,下层其实不会收到错误而是会继续执行下去:

当最上层的 Goroutine 因为某些原因执行失败时,下两层的 Goroutine 由于没有接收到这个信号所以会继续工作;但是当我们正确地使用 Context 时,就可以在下层及时停掉无用的工作减少额外资源的消耗:

 

1.2. Context接口

context.Context 是 Go 语言在 1.7 版本中引入标准库的接口1,该接口定义了四个需要实现的方法,其中包括:

Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;
Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
Err — 返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;如果 context.Context 被取消,会返回 Canceled 错误;如果 context.Context 超时,会返回 DeadlineExceeded 错误;
Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;

Context接口
 // A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
	// Deadline returns the time when work done on behalf of this context
	// should be canceled. Deadline returns ok==false when no deadline is
	// set. Successive calls to Deadline return the same results.
	Deadline() (deadline time.Time, ok bool)

	// Done returns a channel that's closed when work done on behalf of this
	// context should be canceled. Done may return nil if this context can
	// never be canceled. Successive calls to Done return the same value.
	// The close of the Done channel may happen asynchronously,
	// after the cancel function returns.
	//
	// WithCancel arranges for Done to be closed when cancel is called;
	// WithDeadline arranges for Done to be closed when the deadline
	// expires; WithTimeout arranges for Done to be closed when the timeout
	// elapses.
	//
	// Done is provided for use in select statements:
	//
	//  // Stream generates values with DoSomething and sends them to out
	//  // until DoSomething returns an error or ctx.Done is closed.
	//  func Stream(ctx context.Context, out chan<- Value) error {
	//  	for {
	//  		v, err := DoSomething(ctx)
	//  		if err != nil {
	//  			return err
	//  		}
	//  		select {
	//  		case <-ctx.Done():
	//  			return ctx.Err()
	//  		case out <- v:
	//  		}
	//  	}
	//  }
	//
	// See https://blog.golang.org/pipelines for more examples of how to use
	// a Done channel for cancellation.
	Done() <-chan struct{}

	// If Done is not yet closed, Err returns nil.
	// If Done is closed, Err returns a non-nil error explaining why:
	// Canceled if the context was canceled
	// or DeadlineExceeded if the context's deadline passed.
	// After Err returns a non-nil error, successive calls to Err return the same error.
	Err() error

	// Value returns the value associated with this context for key, or nil
	// if no value is associated with key. Successive calls to Value with
	// the same key returns the same result.
	//
	// Use context values only for request-scoped data that transits
	// processes and API boundaries, not for passing optional parameters to
	// functions.
	//
	// A key identifies a specific value in a Context. Functions that wish
	// to store values in Context typically allocate a key in a global
	// variable then use that key as the argument to context.WithValue and
	// Context.Value. A key can be any type that supports equality;
	// packages should define keys as an unexported type to avoid
	// collisions.
	//
	// Packages that define a Context key should provide type-safe accessors
	// for the values stored using that key:
	//
	// 	// Package user defines a User type that's stored in Contexts.
	// 	package user
	//
	// 	import "context"
	//
	// 	// User is the type of value stored in the Contexts.
	// 	type User struct {...}
	//
	// 	// key is an unexported type for keys defined in this package.
	// 	// This prevents collisions with keys defined in other packages.
	// 	type key int
	//
	// 	// userKey is the key for user.User values in Contexts. It is
	// 	// unexported; clients use user.NewContext and user.FromContext
	// 	// instead of using this key directly.
	// 	var userKey key
	//
	// 	// NewContext returns a new Context that carries value u.
	// 	func NewContext(ctx context.Context, u *User) context.Context {
	// 		return context.WithValue(ctx, userKey, u)
	// 	}
	//
	// 	// FromContext returns the User value stored in ctx, if any.
	// 	func FromContext(ctx context.Context) (*User, bool) {
	// 		u, ok := ctx.Value(userKey).(*User)
	// 		return u, ok
	// 	}
	Value(key any) any
}

2. 实例

2.1.取消上下文的控制context.WithCancel

go代码如下,控制一个吃汉堡函数调用的结束:

结束调试是个数》=10的时候

控制函数调用的结束
 package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	eatNum := chiHanBao(ctx)

	for n := range eatNum {
		if n>=10{
			cancel()
			break
		}
		fmt.Print("n,", n)
		time.Sleep(1 * time.Second)
	}


	fmt.Println("正在统计结果。。。")
	time.Sleep(1 * time.Second)
}

func chiHanBao(ctx context.Context) <-chan int {
	c := make(chan int)
	// 个数
	n := 0
	// 时间
	t := 0
	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n)
				return
			case c <- n:
				incr := 1
				n += incr
				t++
				fmt.Printf("我吃了 %d 个汉堡\n", n)
			}
		}
	}()

	return c
}

输出:

n,0我吃了 1 个汉堡
n,1我吃了 2 个汉堡
n,2我吃了 3 个汉堡
n,3我吃了 4 个汉堡
n,4我吃了 5 个汉堡
n,5我吃了 6 个汉堡
n,6我吃了 7 个汉堡
n,7我吃了 8 个汉堡
n,8我吃了 9 个汉堡
n,9我吃了 10 个汉堡
正在统计结果。。。
我吃了 11 个汉堡
耗时 11 秒,吃了 11 个汉堡 
Exiting.

 

 

2.2.超时上下文的控制context.WithTimeout 

go代码如下,控制一个吃汉堡函数调用的结束:

控制吃汉堡结束时机:10秒以后
package main

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

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	chiHanBao2(ctx)
	defer cancel()
}

func chiHanBao2(ctx context.Context) {
	n := 0
	for {
		select {
		case <-ctx.Done():
			fmt.Println("stop \n")
			return
		default:
			n += 1
			fmt.Printf("我吃了 %d 个汉堡\n", n)
		}
		time.Sleep(time.Second)
	}
}

效果:

我吃了 1 个汉堡
我吃了 2 个汉堡
我吃了 3 个汉堡
我吃了 4 个汉堡
我吃了 5 个汉堡
我吃了 6 个汉堡
我吃了 7 个汉堡
我吃了 8 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
stop 

 

2.3.期限结束上下文的控制context.WithDeadline

go代码如下,控制一个吃汉堡函数调用的结束:

停止吃汉堡的期限在10秒后
 package main

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

func main() {
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
	chiHanBao2(ctx)
	defer cancel()
}

func chiHanBao2(ctx context.Context) {
	n := 0
	for {
		select {
		case <-ctx.Done():
			fmt.Println("stop \n")
			return
		default:
			incr := 1
			n += incr
			fmt.Printf("我吃了 %d 个汉堡\n", n)
		}
		time.Sleep(time.Second)
	}
}
我吃了 1 个汉堡
我吃了 2 个汉堡
我吃了 3 个汉堡
我吃了 4 个汉堡
我吃了 5 个汉堡
我吃了 6 个汉堡
我吃了 7 个汉堡
我吃了 8 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
stop 

2.4.上下文值的传递

通过上下文值的传递,可以捕捉整个调用栈:

函数间通过context传递值
 package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.WithValue(context.Background(), "trace_id", "88888888")
	// 携带session到后面的程序中去
	ctx = context.WithValue(ctx, "session", 1)

	fmt.Print("ctx, string", ctx)

	process(ctx)
}

func process(ctx context.Context) {
	session, ok := ctx.Value("session").(int)
	fmt.Println(ok)
	if !ok {
		fmt.Println("something wrong")
		return
	}

	if session != 1 {
		fmt.Println("session 未通过")
		return
	}

	traceID := ctx.Value("trace_id").(string)
	fmt.Println("traceID:", traceID, "-session:", session)
}

效果:

traceID: 88888888 -session: 1

 

标签:context,ctx,golang,Done,Context,time,汉堡,上下文
From: https://www.cnblogs.com/zhanchenjin/p/17090165.html

相关文章

  • Java多线程并发03—线程上下文,线程调度
    多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,如何让用户感觉这些任务正在同时进行呢?操作系统的设计者巧妙地利......
  • 【小记】如果 golang 内存不够了怎么办
    在看redis1.0源码时,总会看到需要申请内存的地方,如果申请不到需要大的内存就会返回NULL,然后在调用层抛出oom。比如listDup中在复制特殊value或者加入新节点时都有......
  • Golang入门第二天
    选择结构循环结构流程控制类型转换类型别名函数调用函数类型匿名函数和闭包回调函数packagemainimport( "fmt")//函数1funcdemo1(){ fmt.Println(......
  • [golang]filepath.Glob的缺陷,不支持多级目录
    最近在使用Gin框架的模板加载过程中,发现其对于多级子目录中的模板支持有问题(仅仅支持一级子目录),后经过查看其源码发现是filepath包的Glob方法的问题。下面先说结论:多......
  • BFC - 块级格式上下文
    1.什么是BFC?浮动元素和绝对定位元素,非块级盒子的块级容器,以及overflow值不为visible的块级盒子,都会为他们的内容创建新的BFC2.触发条件?根元素浮动元素......
  • go context 了解 上下文
      了解上下文(go-rod.github.io)https://go-rod.github.io/#/understand-context 在了解上下文之前,请确保您已学习 Goroutines 和 Channels。上下文主要用于......
  • Golang入门第一天
    变量的使用自动推导类型多重赋值和匿名变量常量的使用多重变量或常量的定义iota枚举bool布尔类型浮点型字符类型字符串类型字符类型和字符串类型的区别复数类......
  • ServletContext获取服务器文件路径 文件下载需求分析
    ServletContext获取服务器文件路径方法:String getRealPath(Stringpath) 文件下载需求1页面显示超链接2点击超链接后弹出下载提示框3完成图片文件......
  • ServletContext功能 域对象2
    ServletContext功能:获取MIME类型:在互联网通信过程中定义的一种文件数据类型格式 大类型/小类型text/htmlimage/jpeg获取:String getMimeType  域对......
  • 上下文切换
    上下文切换 1.什么是上下文?Linux是一个多任务的操作系统,它支持远大于CPU数量的任务同时运行,当然,这些任务实际上并不是真正的在同时运行,而是系统在很短的时间内,将CPU轮流......