七叶笔记 » golang编程 » Golang context简单教程

Golang context简单教程

Context包用作上下文管理,在API边界或者goroutine之间创建信息。主要用作数据传递,截止时间,取消信号等相关的操作。

Context是一个接口,它的定义如下:

 type Context interface {
    Deadline() (deadline time.Time, ok bool)

    Done() <-chan struct{}

    Err() error

    Value(key interface{}) interface{}
}  

Deadline定义了在什么时间点,该操作需要被取消。当没有定义deadline的时候,ok返回的是false。

Done返回一个chan,如果这个chan可以读取(由于parent close了这个chan导致),说明应该取消操作,释放资源。

Err返回的是操作取消的原因。

Value用来在goroutine间或API之间传递值。

Context接口还定义了这些函数:

 func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)  

根据函数的名字,一看就知道其作用。

废话不多说,下面我们通过例子来学习Context的使用。

 package main

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

type key int

const (
// NAME name key
NAME key = iota
)

func sayHello(ctx context.Context) {
name := fmt.Sprintf("%v", ctx.Value(NAME))

for {
select {
case <-ctx.Done():
fmt.Println("routine cancelled - reason:", ctx.Err())
return
case <-time.After(2 * time.Second):
fmt.Println("hello ", name)
}
}
}

func main() {
// 创建一个带Value的context
ctx := context.WithValue(context.Background(), NAME, "bob")
// 从刚创建的context派生出一个返回取消函数的context
ctx, cancel := context.WithCancel(ctx)
go sayHello(ctx)

time.Sleep(10 * time.Second)
// 调用取消函数,取消子routine
cancel()
time.Sleep(3 * time.Second)
fmt.Println("main routine exit")
}  

上面的例子,我们首先创建了一个带Value的context,这里演示了如何在routine间通过context传递数据。然后在通过刚创建的context派生出一个WithCancel的context。通过返回的cancel函数,main routine可以随时取消子routine。我们这里的子routine会一直打印 hello bob ,直到10秒之后,main routine通知取消。子routine退出时,打印出了退出的原因。通过ctx.Err()函数。

将上面的代码保存为context_demo.go然后运行一下,结果如下:

 $ go run context_demo.go
hello  bob
hello  bob
hello  bob
hello  bob
routine cancelled - reason: context canceled
main routine exit  

是不是很简单。

我们下面演示一下WithTimeout的用法,稍微修改一下上面的例子:

 package main

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

type key int

const (
// NAME name key
NAME key = iota
)

func sayHello(ctx context.Context) {
name := fmt.Sprintf("%v", ctx.Value(NAME))

for {
select {
case <-ctx.Done():
fmt.Println("routine cancelled - reason:", ctx.Err())
return
case <-time.After(2 * time.Second):
fmt.Println("hello ", name)
}
}
}

func main() {
// 创建一个带Value的context
ctx := context.WithValue(context.Background(), NAME, "bob")
// 从刚创建的context派生出一个超时取消的context
ctx, _ = context.WithTimeout(ctx, 10*time.Second)
go sayHello(ctx)

time.Sleep(10 * time.Second)
time.Sleep(3 * time.Second)
fmt.Println("main routine exit")
}  

注意,这个例子里面没有调用cancel函数,而是等待超时。将代码保存为context_demo1.go然后运行一下:

 $ go run context_demo1.go
hello  bob
hello  bob
hello  bob
hello  bob
routine cancelled - reason: context deadline exceeded
main routine exit  

可以看到上面退出时打印的reason已经变成了 context deadline exceeded 而不是之前的 context canceled

WithDeadline和超时的差不多,就不举例了。

是不是很简单,赶紧试试吧。

相关文章