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上下文的使用总结。
(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实现链路追踪数据处理的场景。