设为首页 加入收藏

TOP

golang标准库 context的使用(二)
2019-01-31 22:08:25 】 浏览:378
Tags:golang 标准 context 使用
超时之后会自动close对象的Done,与调用CancelFunc的效果一样 // WithDeadline 明确地设置一个d指定的系统时钟时间,如果超过就触发超时 // WithTimeout 设置一个相对的超时时间,也就是deadline设为timeout加上当前的系统时间 // 因为两者事实上都依赖于系统时钟,所以可能存在微小的误差,所以官方不推荐把超时间隔设置得太小 // 通常这样调用ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) // 带有值的context,没有CancelFunc,所以它只用于值的多goroutine传递和共享 // 通常这样调用ctx := context.WithValue(context.Background(), "key", myValue) func WithValue(parent Context, key, val interface{}) Context

对于会返回CancelFunc的函数,我们必须要使用defer cancel(),否则静态检查例如go vet会报错,理由是因为如果不用defer来终止context的话不能避免goroutine leak,对于带有超时的context来说cancel还可以停止计时器释放对应的资源。另外多次调用cancel是无害的,所以及时一个context因为超时而被取消,你依然可以对其使用cancel。所以我们应该把cancel的调用放在defer语句中。

上面是在主goroutine中的处理,对于传入context的goroutine来说需要做一些结构上的改变:

func coroutine(ctx context.Context, data <-chan int) {
  // setup something
  for {
    select {
    case <-ctx.Done():
      // 一些清理操作
      return
    case i := <-data:
      go handle(ctx, i)
    }
  }
}

可以看见goroutine的主要逻辑结构需要由select包裹,首先检查本次任务有没有取消,没有取消或者超时就从chan里读取数据进行处理,如果需要启动其他goroutine就把ctx传递下去。

golang的初学者可能会对这段代码产生不少疑惑,但是等熟悉了goroutine+chan的使用后就会发现这只是对既有模型的微调,十分便于迁移和修改。

示例

虽然说了这么多,实际上还都是些很抽象的概念,所以这一节举几个例子辅助理解。

首先是使用超时context的例子,每个goroutine运行5秒,每隔一秒打印一段信息,5秒后终止运行:

func coroutine(ctx context.Context, duration time.Duration, id int, wg *sync.WaitGroup) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("goroutine %d finish\n", id)
            wg.Done()
            return
        case <-time.After(duration):
            fmt.Printf("message from goroutine %d\n", id)
        }
    }
}

func main() {
    wg := &sync.WaitGroup{}
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    defer cancel()

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go coroutine(ctx, 1 * time.Second, i, wg)
    }

    wg.Wait()
}

我们使用WaitGroup等待所有的goroutine执行完毕,在收到<-ctx.Done()的终止信号后使wg中需要等待的goroutine数量减一。因为context只负责取消goroutine,不负责等待goroutine运行,所以需要配合一点辅助手段。如果运行程序你会得到类似如下结果(不同环境运行结果可能不同):

message from goroutine 0
message from goroutine 2
message from goroutine 4
message from goroutine 3
message from goroutine 1
message from goroutine 2
message from goroutine 4
message from goroutine 0
message from goroutine 1
message from goroutine 3
message from goroutine 3
message from goroutine 0
message from goroutine 4
message from goroutine 2
message from goroutine 1
message from goroutine 0
message from goroutine 2
message from goroutine 4
message from goroutine 3
message from goroutine 1
goroutine 0 finish
goroutine 3 finish
goroutine 1 finish
goroutine 2 finish
goroutine 4 finish

上一个例子中示范了超时控制,下一个例子将会演示如何用普通context取消一个goroutine:

func main() {
    // gen是一个生成器,返回从1开始的递增数字直到自身被取消
    gen := func(ctx context.Context) <-chan int {
        dst := make(chan int)
        n := 1
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    for n := range gen(ctx) {
        fmt.Println(n)
        // 生成到5时终止生成器运行
        if n == 5 {
            break
        }
    }
}

运行结果将会输出1-5的数字,当生成5之后for循环终止,main退出前defer语句生效,终止goroutine的运行。

最后一个例子是如何在goroutine间共享变量的。

因为可能会被多个goroutine同时修改,所以我们的value必须保证并发安全,不过也可以换种思路,只要保证对value的操作是并发安全的就可以了:

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[Go] golang的竞争状态 下一篇[Go] go get获取官方库被墙解决

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目