分享
  1. 首页
  2. 文章

golang学习笔记之-context

Maggie_up · · 1318 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Context-用来管理调用上下文,控制一个请求的生命周期。

直接看代码:Context是一个接口

type Context interface { 
 //返回代表该Context过期的时间,和表示deadline是否被设置的bool值。
 Deadline() (deadline time.Time, ok bool)
 //返回一个channel,关闭该channel就代表关闭该Context。返回nil代表该Context不需要被关闭
 Done() <-chan struct{}
 Err() error
 Value(key interface{}) interface{}
}

Context这个接口共有4个方法:

  • Deadline方法是获取设置的截止时间,第一个参数返回式是截时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
  • Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。
  • Err方法返回取消的错误原因,因为什么Context被取消。
  • Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。

以上四个方法中常用的就是Done。如果Context取消的时候,我们就可以得到一个关闭的chan,关闭的chan是可以读取的,所以只要可以读取的时候,就意味着收到Context取消的信号了,以下是这个方法的经典用法。

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:
 }
 }
 }

Context接口并不需要我们实现,Go内置已经帮我们实现了2个,我们代码中最开始都是以这两个内置的作为最顶层的partent context,衍生出更多的子Context。这些 Context 对象形成一棵树:当一个 Context 对象被取消时,继承自它的所有 Context 都会被取消。两个实现如下:

var (
 background = new(emptyCtx)
 todo = new(emptyCtx)
)
func Background() Context {
 return background
}
func TODO() Context {
 return todo
}
  • Background:主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。
  • TODO:它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。

上面Background和TODO方法:都是emptyCtx结构体类型,是一个不可取消。没有设置截止时间,没有携带任何值的Context。

// emptyCtx 不需要关闭,没有任何键值对,也没有过期时间。
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
 return
}
func (*emptyCtx) Done() <-chan struct{} {
 return nil
}
func (*emptyCtx) Err() error {
 return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
 return nil
}

emptyCtx实现Context接口的方法:可以看到这些方法什么都没做,返回的都是nil或者零值。

Context的继承衍生

有了如上的根Context,那么是如何衍生更多的子Context的呢?这就要靠context包为我们提供的With系列的函数了。

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
  • 这四个With函数:接收的都有一个partent参数,就是父Context。我们要基于这个父Context创建出子Context的意思,这种方式可以理解为子Context对父Context的继承,也可以理解为基于父Context的衍生。
  • 通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。
  • WithCancel函数:传递一个父Context作为参数,返回子Context以及一个取消函数用来取消Context。
  • WithDeadline函数:和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。
  • WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。
  • WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,后面我们会专门讲。

大家可能留意到,前三个函数都返回一个取消函数CancelFunc,这是一个函数类型。

type CancelFunc func()

这就是取消函数的类型:该函数可以取消一个Context,以及这个节点Context下所有的所有的Context,不管有多少层级。


WithValue传递元数据

通过Context我们也可以传递一些必须的元数据,这些数据会附加在Context上以供使用。

var key string="name"
func main() {
 ctx, cancel := context.WithCancel(context.Background())
 //附加值
 valueCtx:=context.WithValue(ctx,key,"【监控1】")
 go watch(valueCtx)
 time.Sleep(10 * time.Second)
 fmt.Println("可以了,通知监控停止")
 cancel()
 //为了检测监控过是否停止,如果没有监控输出,就表示停止了
 time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
 for {
 select {
 case <-ctx.Done():
 //取出值
 fmt.Println(ctx.Value(key),"监控退出,停止了...")
 return
 default:
 //取出值
 fmt.Println(ctx.Value(key),"goroutine监控中...")
 time.Sleep(2 * time.Second)
 }
 }
}
  • 在前面的例子,我们通过传递参数的方式,把name的值传递给监控函数。在这个例子里,我们实现一样的效果,但是通过的是Context的Value的方式。
  • 我们可以使用context.WithValue方法附加一对K-V的键值对,这里Key必须是等价性的,也就是具有可比性;Value值要是线程安全的。
  • 这样我们就生成了一个新的Context,这个新的Context带有这个键值对,在使用的时候,可以通过Value方法读取ctx.Value(key)。
  • 记住:使用WithValue传值,一般是必须的值,不要什么值都传递。

Context 使用原则

  • 不要把Context放在结构体中,要以参数的方式传递
  • 以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。
  • 给一个函数方法传递Context的时候,不要传递nil。如果不知道传递什么,就使用context.TODO
  • Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递
  • Context是线程安全的,可以放心的在多个goroutine中传递

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:Maggie_up

查看原文:golang学习笔记之-context

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1318 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏