GO 语言中 Context 包详解

Rreality is merely an illusion, albeit a very persistent one.

前言

contextcontext.TODO()Context
Context
GVAContext
Context

埋头苦学往往只是知其形而不知其意,只有实际做项目才能够真正的掌握一门语言

一、Context 介绍

1.1 Context 是什么?

ContextGo1.7GoGoroutineGoroutine
GogoroutinegoroutinegoroutineRPCgoroutinetokengoroutineGooglecontextgoroutine
Contextgoroutinegoroutinechannelgoroutinedone channel
ContextContextContext

Context 使用场景

  • 上下文信息传递 (request-scoped),比如处理 http 请求、在请求处理链路上传递信息
  • 控制子 goroutine 的运行
  • 超时控制的方法调用
  • 可以取消的方法调用

1.2 Context 接口

contextContext
// A Context carries a deadline, cancellation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}
DonechannelcontextchannelDoneErrerrorcontextDone channelDeadlinedeadlineValuecontextgoroutine
DonecloseErrnilDonecloseErrDoneclose

二、Context 的使用

GoogleContextGoContextgoroutine
contextContext
  • context.Background()
  • context.TODO()

这两个函数互为别名,没有差别,官方定义:

context.Backgroundcontext.TODO()
contextcontextwith
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
ContextContext

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SslcluBn-1656939722939)(./images/context-derive.png)]

2.1 WithValue 携带数据、Value 获取数据

GVAcontext
// InitDB 创建数据库并初始化 总入口
func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {
	ctx := context.TODO()

	var initHandler TypedDBInitHandler
	
	initHandler = NewMysqlInitHandler()
	ctx = context.WithValue(ctx, "dbtype", "mysql")
	
	ctx, _ = initHandler.EnsureDB(ctx, &conf)
	
	db := ctx.Value("db").(*gorm.DB)
	global.GVA_DB = db

	if err = initHandler.WriteConfig(ctx); err != nil {
		return err
	}
	if err = initHandler.InitTables(ctx, initializers); err != nil {
		return err
	}
	if err = initHandler.InitData(ctx, initializers); err != nil {
		return err
	}

	return nil
}

// EnsureDB 创建数据库并初始化 mysql
func (h MysqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
	if s, ok := ctx.Value("dbtype").(string); !ok || s != "mysql" {
		return ctx, ErrDBTypeMismatch
	}
	c := conf.ToMysqlConfig()
	next = context.WithValue(ctx, "config", c)
	next = context.WithValue(next, "db", db)
}


// WriteConfig mysql回写配置
func (h MysqlInitHandler) WriteConfig(ctx context.Context) error {
	c, _ := ctx.Value("config").(config.Mysql)

	global.GVA_CONFIG.Mysql = c
	return global.GVA_VP.WriteConfig()
}
context.TODO()ctxcontext.WithValuedbtypeconfigdbcontextcontextcontext
context
InitDBdbtypeEnsureDBValuedbtypeEnsureDBconfigdbWriteConfigconfigInitDBdbgorm.DB
withValue
contextvaluekeyvaluecontextcontextkeycontextcontextcontextnilcontextkeyvalueinterface

2.2 WithCancel 取消控制

goroutinegoroutinewithCancelcontextgoroutinegoroutinecancel
InitTablesInitData
func (h MysqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
	return createTables(ctx, inits)
}
// createTables 创建表(默认 dbInitHandler.initTables 行为)
func createTables(ctx context.Context, inits initSlice) error {
	next, cancel := context.WithCancel(ctx)
	defer func(c func()) { c() }(cancel)
    return nil
}

func (h MysqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
	next, cancel := context.WithCancel(ctx)
	defer func(c func()) { c() }(cancel)
    return nil
}
GVAgoroutinecontext
contextgoroutine
func main()  {
    ctx,cancel := context.WithCancel(context.Background())
    go CancelText(ctx)
    time.Sleep(10*time.Second)
    cancel()
    time.Sleep(1*time.Second)
}

func CancelText(ctx context.Context)  {
    for range time.Tick(time.Second){
        select {
        case <- ctx.Done():
            fmt.Println("stop time")
            return
        default:
            fmt.Println("start time")
        }
    }
}
WithCancelBackgroundctxmainCancelTextstart time

主协程在 10s 后执行 cancel,CancelText 子协程检测到取消信号后退出

参考