go context

在Go里, 我们不能直接杀死协程, 协程的关闭一般会用channel+select方式/或者runtime.Goexit()来控制. 虽然能打到目的,但是如果在复杂派生场景下,就会显得乏力了. 于是就引出了Context:

context接口

1.分类

	1.可被控制类
		1.emptyCtx{backgroundCtx,todoCtx} ps:不可取消,没有设置截止时间,没有携带任何值的Ctx
	2.不可被控制类
		1.cancelCtx{cancelCtx}
		2.timerCtx{deadlineCtx,timeoutCtx}
		3.valueCtx{valueCtx}

2.接口

	1.context接口(所有ctx都实现了)
		1.deadline方法
			返回ctx的deadline,以及ctx是否会被取消;没有设置deadline的话,ok==false
		2.done方法
			ctx被取消后,返回一个被关闭的channel
		3.err方法
			返回ctx被取消的原因
		4.value方法
			返回参数key对应的value
	2.canceler接口(只有cancelCtx和timerCtx实现了)
		实现了这个接口表明该ctx是可以被取消的
		# context接口

四个方法:

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Donne() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

1.Deadline()
	func (context.Context).Deadline() (deadline time.Time, ok bool)
		deadline:	返回当前Context被取消的时间
		ok:			如果没有设置deadline,则返回ok==false: 不会被取消

2. Done()
	func (context.Context).Done() <-chan struct{}
		context被取消的信号(是否执行cancelFunc):
			1. 执行cancelFunc						(即 context被取消):
					将返回一个关闭的channel(),select读取分支从这个关闭的channel读取除了零值(nil)
					读取到了零值就代表context被关闭,于是就执行退出协程.
			2. 不执行cancelFunc						(即context不会被取消):
					将返回一个nil(什么也不返回,更没有channel),
					返回一个nil就代表没有channel可以读, 那么select就直接进入default分支执行!

3. Err()
	func (context.Context).Err() error
		返回一个错误,表示context被关闭的原因:
			1. context未被关闭,返回nil
			2. context被关闭,返回一个非nil错误:
				2.1.Context被取消 -- Canceled;
				2.2.Context超时   -- DeadlineExceeded;

4. Value()
	获取之前设置的key所对应的value

用法

1.创建根ctx

	根ctx的类型:
		1.context.Background()获取background类型ctx
		2.context.Todu()获取todo类型ctx

2.创建子ctx

	子ctx的类型:
		1.caccelCtx
			1.withcancel
				实现接口: context,canceler
				创建方法:context.WithCancel(父Ctx)(withcancelCtx, 当前ctx的取消函数)
		2.timerCtx(timerCtx基于cancelCtx,只是多了一个time.Timer和一个deadline)
			1.deadlineCtx
				实现接口: context,canceler
				创建方法:context.WithDeadline(父Ctx)...
			2.timeoutCtx
				实现接口: context,canceler
				创建方法:context.WithTimeout(父Ctx)...
		3.valueCtx
			1.valueCtx
				实现接口: context
				创建方法:context.WithValue(父Ctx)...

案例

package main

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

/*
func context.WithCancel(parent context.Context) (ctx context.Context, cancel context.CancelFunc)
	参数:
		parent:	父Context,可以使用background或者TODO作为根Context!
	返回值:
		ctx:	返回根据父Context创建的子Context
		cancel:	取消函数

*/

func main() {
	// 1. 根ctx
	ctx0 := context.Background()

	// 2. 创建子Ctx,类型为withcancel
	ctx1, cancel1 := context.WithCancel(ctx0)

	// 3. 传入子withcancel到go1协程中
	go go1(ctx1)

	// 4. 创建孙子Ctx,类型为withcancel
	ctx2, cancel2 := context.WithCancel(ctx1)
	_ = cancel2

	// 5. 传入孙子withcancel到go2协程中
	go go2(ctx2)

	// 6. 取消ctx1
	time.Sleep(time.Second * 22)
	cancel1() // 当父Context关闭的时候,其派生的所有子Context也会被关闭!

	// 7.查看ctx1被关闭的原因
	err := ctx1.Err()
	if err != nil {
		fmt.Printf("%v: levelOne_context被关闭的原因:%v\n", time.Now().UTC().Unix(), err)
	}
}

// go1
func go1(ctx context.Context) {
	count := 1
	for {
		select {
		case <-ctx.Done(): // 接收ctx关闭信号
		default: // 执行任务
			time.Sleep(time.Second * 5)
			fmt.Printf("%v: levelOne_goroutine正在执行%d次任务!\n", time.Now().UTC().Unix(), count)
			count++
		}
	}
}

// go2
func go2(ctx context.Context) {
	count := 1
	for {
		select {
		case <-ctx.Done(): // 返回关闭的chan --> 关闭当前协程 (啥都不需要做,goroutine会自动关闭)
		default: //               返回nil		 --> 执行goroutine的任务
			time.Sleep(time.Second * 5)
			fmt.Printf("%v: levelTwo_gotoutine正在执行%d次任务!\n", time.Now().UTC().Unix(), count)
			count++
		}
	}
}

### Go语言 `context` 包使用教程 #### 创建根上下文 在Go语言中,程序通常会从两个预定义函数获取顶级上下文:`background()` 或者 `todo()`。这两个函数返回的上下文对象一般作为整个调用链最顶层的起点[^3]。 ```go package main import ( "context" ) func main() { ctx := context.Background() } ``` #### 添加截止时间到上下文中 通过 `WithDeadline` 方法可以为某个操作设置一个结束的时间点,在这个时间之后,所有的子goroutine都会收到通知并停止工作[^1]。 ```go package main import ( "context" "fmt" "time" ) func main() { d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) } } ``` #### 取消子协程执行 利用 `WithCancel` 函数可以从父goroutine主动触发取消事件给所有依赖于该上下文的子goroutine,从而实现优雅关闭资源的目的。 ```go package main import ( "context" "fmt" "time" ) func worker(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("worker exiting:", ctx.Err()) return default: fmt.Println("working...") time.Sleep(time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx) time.Sleep(3 * time.Second) cancel() time.Sleep(time.Second) } ``` #### 值传递 有时候需要向下游服务传递一些不敏感的数据,这时就可以借助 `WithValue` 来附加键值对形式的信息至上下文中[^5]。 ```go package main import ( "context" "fmt" ) type userKey string func main() { type key int userID := key(42) ctx := context.WithValue(context.Background(), userID, "jane") v := ctx.Value(userID).(string) fmt.Println(v == "jane") // true } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值