golang-context(上下文总结)

<一>go-Context使用笔记:

1.context.WithCancel()

功能:返回一个继承的Context,在父协程context的Done函数被关闭时会关闭自己的Done通道,或者在执行了如下cancel函数之后,会关闭自己的Done通道。这种关闭的通道可以作为一种广播的通知操作,告诉所有context相关的函数停止当前的工作直接返回。通常使用场景用于主协程用于控制子协程的退出,用于一对多处理。

用法:

ctx,cancel := context.WithCancel(context.Background())

defer cancel()

举例:主协程序控制通知子协程序安全退出

package main

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

func main() {

	// 控制子协程安全的退出,调用cancle后,会关闭自己的通道,表示程序结束,所有子协程会安全的退出
	ctx, cancle := context.WithCancel(context.Background())

	defer cancle() // 取消函数上下文

	go func() {
		for {
			select {
            // ctx为一个接口类型,存储的就是一个cancelCtx结构的地址,所以,表面看起来就是一个值传递,实质上就是地址,接口接受很好表现了封装完整性
			case <-ctx.Done():
				return
			default:
				fmt.Println("go first ", reflect.TypeOf(ctx).Elem().Name())
			}

			time.Sleep(time.Second)
		}
	}()

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
				fmt.Println("go second ", reflect.TypeOf(ctx).Elem().Name())
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
				fmt.Println("go third ", reflect.TypeOf(ctx).Elem().Name())
			}
			time.Sleep(time.Second)
		}
	}()

	fmt.Println("main-",reflect.TypeOf(ctx).Elem())
	time.Sleep(5 * time.Second)
}

运行结果:

main- context.cancelCtx
go first  cancelCtx
go second  cancelCtx
go third  cancelCtx
go third  cancelCtx
go second  cancelCtx
go first  cancelCtx
go second  cancelCtx
go first  cancelCtx
go third  cancelCtx
go third  cancelCtx
go first  cancelCtx
go second  cancelCtx
go second  cancelCtx
go third  cancelCtx
go first  cancelCtx

2.context.WithDeadline()

功能:传递一个上下文,等待超时时间,超时后,会返回超时时间,并且会关闭context的Done通道,其他传递的context,收到Done关闭的消息的,直接返回即可。同样用户通知消息出来。

用法:

ctx, cancle := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))

defer cancle()

举例:

package main

import (
	"context"
	"log"
	"os"
	"time"
)

var logg *log.Logger

func main() {

	logg = log.New(os.Stdout, "", log.Ltime)
	// 设置一个上下文,并设置对应的超时时间
	ctx, cancle := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))

	defer cancle()
	go func() {
		for {
			select {
			case <-ctx.Done():
				logg.Printf("son go is end !")
				return
			}
		}
	}()

	time.Sleep(8 * time.Second)

}

3.context.WithTimeout()

功能:传递一个上下文,并且设置对应的超时时间,调用Deadline()判断当前上下文是否超时

同样用于通知消息进行处理,控制上下文的处理。

用法:

	// 定义一个超时上下文,指定相应的超时时间
	ctx, cancle := context.WithTimeout(context.Background(), 5*time.Second)

	defer cancle()

举例:

package main

import (
	"context"
	"log"
	"time"
)

func main() {

	// 定义一个超时上下文,指定相应的超时时间
	ctx, cancle := context.WithTimeout(context.Background(), 5*time.Second)

	defer cancle()
	go func() {
		for {

			time.Sleep(1 * time.Second)
			// 检查ctx何时会超时
			if deadline, ok := ctx.Deadline(); ok {
				log.Print("deadline !", deadline)

				// 判断当前时间是不是在ctx取消之后,直接终止该函数,此处判断超时空取消了ctx,可以直接退出返回.
				if time.Now().After(deadline) {
					log.Printf(ctx.Err().Error())
					return
				}

			}
			select {
			case <-ctx.Done():
				log.Print("done !")
                // return // 没有上面推出,可在此处退出函数
			default:
				log.Print("son !!!")

			}
		}

	}()

	time.Sleep(8 * time.Second)
}

运行结果:

2021/06/16 20:02:48 deadline !2021-06-16 20:02:52.279309 +0800 CST m=+5.000190125
2021/06/16 20:02:48 son !!!
2021/06/16 20:02:49 deadline !2021-06-16 20:02:52.279309 +0800 CST m=+5.000190125
2021/06/16 20:02:49 son !!!
2021/06/16 20:02:50 deadline !2021-06-16 20:02:52.279309 +0800 CST m=+5.000190125
2021/06/16 20:02:50 son !!!
2021/06/16 20:02:51 deadline !2021-06-16 20:02:52.279309 +0800 CST m=+5.000190125
2021/06/16 20:02:51 son !!!
2021/06/16 20:02:52 deadline !2021-06-16 20:02:52.279309 +0800 CST m=+5.000190125
2021/06/16 20:02:52 After%!(EXTRA string=context deadline exceeded)

 使用:<-ctx.Done(),结束协程

运行结果如下:

2021/06/16 20:06:54 deadline !2021-06-16 20:06:58.129238 +0800 CST m=+5.000237580
2021/06/16 20:06:54 son !!!
2021/06/16 20:06:55 deadline !2021-06-16 20:06:58.129238 +0800 CST m=+5.000237580
2021/06/16 20:06:55 son !!!
2021/06/16 20:06:56 deadline !2021-06-16 20:06:58.129238 +0800 CST m=+5.000237580
2021/06/16 20:06:56 son !!!
2021/06/16 20:06:57 deadline !2021-06-16 20:06:58.129238 +0800 CST m=+5.000237580
2021/06/16 20:06:57 son !!!
2021/06/16 20:06:58 deadline !2021-06-16 20:06:58.129238 +0800 CST m=+5.000237580
2021/06/16 20:06:58 done !

4.context.WithValue()

功能:用户传递上下文的消息信息,将需要传递的消息从一个协程传递到另外协程,引领上下文进行相关业务处理。

用法:

// 设置对应的消息信息k-v
ctx := context.WithValue(context.Background(), "trace_id", "888888")
ctx = context.WithValue(ctx, "session", 1)

举例:

package main

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

func main() {
	ctx := context.WithValue(context.Background(), "name", "eric")
	ctx = context.WithValue(ctx, "session", 100001)

	go func(ctx *context.Context) {
		fmt.Println("start to go process")
		// session
		session, ok := (*ctx).Value("session").(int)
		if ok {
			fmt.Println(ok, "+", session)
		}

		name, ok := (*ctx).Value("name").(string)
		if ok {
			fmt.Println(ok, "+", name)
		}

		fmt.Println("end to go process")

	}(&ctx)

	// 让主协助程序等待子协程退出后,主协程在推出即可
	time.Sleep(time.Second)

}

运行结果:

start to go process
true + 100001
true + eric
end to go process

以上context上下文的使用总结。

<二>Context源码拆解

 (go.1.19)版本

1.Context结构:

type Context interface {
	
	Deadline() (deadline time.Time, ok bool) // 该函数返回一个被取消的时间线,如果没有设置时间线,则ok返回false

	Done() <-chan struct{} // 该函数返回一个struct{}通道,用于不同携程之间传递消息,当通道被关闭之后,会返回0,常跟select结合使用。

	Err() error  // 返回的是通道的关闭原因,通道没关闭,返回nil,通道关闭了,返回其原因,关闭or超时

	Value(key any) any  // 获取通过context传递的k-v值,进行消息传递
}

  2.concelCtx结构:

type cancelCtx struct {
	Context     // 包含一个匿名的Context,所以具有Context的属性
	mu       sync.Mutex            
	done     atomic.Value         
	children map[canceler]struct{}  // 对应的子ctx的创建存储
	err      error                 
}

type canceler interface {
	cancel(removeFromParent bool, err error) // 如果父ctx被取消了,那么其下面的所有子ctx都会取消
	Done() <-chan struct{}
}

3.timeCtx结构:

type timerCtx struct {
	cancelCtx
	timer *time.Timer // 定时器

	deadline time.Time // 取消的时间线
}

4.valueCtx结构:

type valueCtx struct {
	Context  // 拥有context的属性
	key, val interface{}
}

 5.几种定义ctx函数使用的拆解:

 WithCancel函数:

type CancelFunc func() // 返回的cancel函数类型的定义

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := newCancelCtx(parent) // 构建一个cancelCtx结构,关联传入的父ctx
	propagateCancel(parent, &c) // 检查传入的parent的状态是否被取消了
	return &c, func() { c.cancel(true, Canceled) }
}

func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

  如上使用WithCancel创建,如果parent没有被取消,则返回一个cancelCtx,跟一个cancel函数,该函数具体内容如下:

当主动调用cancel直接取消后,其下所有的关联ctx都会取消。(主协程控制子携程退出场景常见使用)

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
	if err == nil {
		panic("context: internal error: missing cancel error")
	}
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return // already canceled
	}
	c.err = err
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		c.done.Store(closedchan)
	} else {
		close(d) // 关闭通道
	}

    // 当前的ctx被取消了,那么其下面的子ctx也全被取消掉
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}

 例如主动取消对应的ctx

func main() {
	ctxParent, cancelParent := context.WithCancel(context.Background()) // 
	ctxChild, _ := context.WithCancel(ctxParent)
	// 调用parent的ctx取消函数
	cancelParent()

	select {
	case <-ctxParent.Done():
		fmt.Println("parents ctx be canceled")
	}
	select {
	case <-ctxChild.Done():
		fmt.Println("son ctx be canceled")
	}
}

结果:
parents ctx be canceled
son ctx be canceled

  WithDeadline函数:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
    // 检查当前ctx的时间线,检查是目前的时间线是否早于新的最后期限
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}

    //构建一个定时ctx
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}
	propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(false, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {   // 构建一个定时器,当超时,会主动取消当前的ctx
			c.cancel(true, DeadlineExceeded) // 对应的函数cancel如下:
		})
	}
	return c, func() { c.cancel(true, Canceled) } // 当然该处返回的cancel,再逻辑层进行主动调用
}


// 定时器到期后,调用该cancel直接将ctx取消掉,从而控制所有的控制子协程退出
func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

 例如:定时器自动调用取消函数:

func main() {
	ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5)) // 定时器5s

	subctx, _ := context.WithCancel(ctx) // 定义一个子ctx

	select {
	case <-ctx.Done():
		fmt.Println("parents is deadline")
	}
	select {
	case <-subctx.Done():
		fmt.Println("subctx is deadline")
	}

}

结论:
parents is deadline
subctx is deadline

WithValue函数:

func WithValue(parent Context, key, val any) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {  // 查询了下,表示当前的key是能够比较的
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val} 
}

// 通过key查询了对应的value
func (c *valueCtx) Value(key any) any {
	if c.key == key {
		return c.val
	}
	return value(c.Context, key) // 根据对应ctx可以递归式查询对应的数据value,该出代码,跟旧版本的有点差异,直接将c.Context当参数进行传递了,
}

  将kv数据传递子协助程中进行访问:

func main() {
	ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5)) // 定时器5s

	kv_ctx := context.WithValue(ctx, "name", "lx")
	select {
	case <-ctx.Done():
		fmt.Println(kv_ctx.Value("name"))
		fmt.Println("parents is deadline")
	}
}

以上就是集中函数的内存源码+使用举例,在go中先关的许多框架都涵盖了上下文的封装,实际使用中用于传递一些数据or实现链路追踪数据处理的场景。 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang 中,可以通过 `context` 包来获取上下文的所有信息。`context` 包提供了一个 `Context` 接口,该接口定义了一组方法,用于获取和设置上下文信息。具体来说,可以通过 `context.Background()` 方法创建一个空的上下文,然后使用 `WithCancel`、`WithDeadline`、`WithTimeout` 或 `WithValue` 等方法来创建一个新的上下文,并在其中设置相应的信息。 例如,以下代码演示了如何创建一个具有超时时间的上下文,并在其中设置一个字符串值: ``` package main import ( "context" "fmt" "time" ) func doSomething(ctx context.Context) { // 获取上下文中的字符串值 value := ctx.Value("key").(string) fmt.Println("value:", value) // 等待上下文超时或被取消 select { case <-ctx.Done(): fmt.Println("context done:", ctx.Err()) case <-time.After(5 * time.Second): fmt.Println("done") } } func main() { // 创建一个具有超时时间的上下文,并设置一个字符串值 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() ctx = context.WithValue(ctx, "key", "value") // 在新的 Goroutine 中执行任务 go doSomething(ctx) // 等待一段时间 time.Sleep(10 * time.Second) } ``` 在这个例子中,我们首先使用 `context.Background()` 方法创建了一个空的上下文,然后使用 `context.WithTimeout()` 方法创建了一个具有 3 秒超时时间的新上下文。接着,我们使用 `context.WithValue()` 方法在新上下文中设置了一个字符串值。最后,我们在新的 Goroutine 中执行了一个任务,并等待一段时间,以便让任务有足够的时间执行。在任务中,我们通过 `ctx.Value()` 方法获取了上下文中的字符串值,并使用 `select` 语句等待上下文超时或被取消。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值