go - context 用法

1,context作用

1,通过context,我们可以方便地对同一个请求所产生地goroutine进行约束管理,可以设定超时、deadline,甚至是取消这个请求相关的所有goroutine。形象地说,假如一个请求过来,需要A去做事情,而A让B去做一些事情,B让C去做一些事情,A、B、C是三个有关联的goroutine,那么问题来了:假如在A、B、C还在处理事情的时候请求被取消了,那么该如何优雅地同时关闭goroutine A、B、C呢?这个时候就轮到context包上场了。

2,在golang中的创建一个新的线程并不会返回像c语言类似的pid所有我们不能从外部杀死某个线程,所有我就得让它自己结束之前我们用channel+select的方式,来解决这个问题但是有些场景实现起来比较麻烦,例如由一个请求衍生出多个线程并且之间需要满足一定的约束关系,以实现一些诸如:

有效期,中止线程树,传递请求全局变量之类的功能。

于是google 就为我们提供一个解决方案,开源了context包。使用context包来实现上下文功能 …..
约定:需要在你的方法的传入参数的第一个参数是context.Context的变量。

2,context结构

使用例子(1):

package main

import (
    "context"
    "fmt"
)

func main() {
    // gen generates integers in a separate goroutine and
    // sends them to the returned channel.
    // The callers of gen need to cancel the context once
    // they are done consuming generated integers not to leak
    // the internal goroutine started by gen.
    // 定义 goroutine 函数
    gen := func(ctx context.Context) <-chan int {
        dst := make(chan int)
        n := 1
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return // returning not to leak the goroutine
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // main 方法执行完后,结束 ctx 相关的 goroutine

    for n := range gen(ctx) {
        fmt.Println(n)
        if n == 5 {
            break
        }
    }
}

使用例子(2):

package main
import (
    "fmt"
    "time"
    "golang.org/x/net/context"
)
func Cdd(ctx context.Context) int {
    fmt.Println(ctx.Value("NLJB"))
    select {
    // 结束时候做点什么 ...
    case <-ctx.Done():
        return -3
    default:
        // 没有结束 ... 执行 ...
    }
}
func Bdd(ctx context.Context) int {
    fmt.Println(ctx.Value("HELLO"))
    fmt.Println(ctx.Value("WROLD"))
    ctx = context.WithValue(ctx, "NLJB", "NULIJIABEI")
    go fmt.Println(Cdd(ctx))
    select {
    // 结束时候做点什么 ...
    case <-ctx.Done():
        return -2
    default:
        // 没有结束 ... 执行 ...
    }
}
func Add(ctx context.Context) int {
    ctx = context.WithValue(ctx, "HELLO", "WROLD")
    ctx = context.WithValue(ctx, "WROLD", "HELLO")
    go fmt.Println(Bdd(ctx))
    select {
    // 结束时候做点什么 ...
    case <-ctx.Done():
        return -1
    default:
        // 没有结束 ... 执行 ...
    }
}
func main() {
    // 自动取消(定时取消)
    {
        timeout := 3 * time.Second
        ctx, _ := context.WithTimeout(context.Background(), timeout)
        fmt.Println(Add(ctx))
    }
    // 手动取消
    //  {
    //      ctx, cancel := context.WithCancel(context.Background())
    //      go func() {
    //          time.Sleep(2 * time.Second)
    //          cancel() // 在调用处主动取消
    //      }()
    //      fmt.Println(Add(ctx))
    //  }
    select {}
}

(1)context.Context 接口

  • context包里的方法是线程安全的,可以被多个线程使用
  • 当Context被canceled或是timeout, Done返回一个被closed 的channel
  • 在Done的channel被closed后, Err代表被关闭的原因
  • 如果存在, Deadline 返回Context将要关闭的时间
  • 如果存在,Value 返回与 key 相关了的值,不存在返回 nil
 // context 包的核心
type Context interface {               
    Done() <-chan struct{}      
    Err() error 
    Deadline() (deadline time.Time, ok bool)
    Value(key interface{}) interface{}
}
  • Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

  • Done方法返回一个信道(channel)。当Context被cancenl或timeout时,该信道被关闭。它是一个表示Context是否已关闭的信号。

  • 当Done信道关闭后,Err方法表明Context被撤的原因。

  • Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

(2)接口的实现

我们不需要手动实现这个接口,context 包已经给我们提供了两个

  • Background
  • TODO

这两个函数都会返回一个Context的实例只是返回的这两个实例都是空 Context。

 /*
    TODO返回一个非nil,empty的上下文
    在目前还不清楚要使用的上下文或尚不可用时
*/
context.TODO()
/*
    Background返回一个非nil,empty的上下文。
    这是没有取消,没有值,并且没有期限。
    它通常用于由主功能,初始化和测试,并作为输入的顶层上下文
*/
context.Background()

3,context包里的方法

(1)方法的使用

Context接口没有提供方法来设置其值和过期时间,也没有提供方法直接将其自身撤销。也就是说,Context不能改变和撤销其自身。那么该怎么通过Context传递改变后的状态呢?

context包里,主要使用的方法:

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 interface{}, val interface{}) Context
  • WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出

  • WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))

  • WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值

Context 的调用应该是链式的,通过WithCancel,WithDeadline,WithTimeout或WithValue派生出新的 Context。当父 Context 被取消时,其派生的所有 Context 都将取消。

通过context.WithXXX都将返回新的 Context 和 CancelFunc。调用 CancelFunc 将取消子代,移除父代对子代的引用,并且停止所有定时器。未能调用 CancelFunc 将泄漏子代,直到父代被取消或定时器触发。go vet工具检查所有流程控制路径上使用 CancelFuncs。

(2)context的创建

所有的context的父对象,也叫根对象,是一个空的context,它不能被取消,它没有值,从不会被取消,也没有超时时间,它常常作为处理request的顶层context存在,然后通过WithCancel、WithTimeout函数来创建子对象来获得cancel、timeout的能力

当顶层的request请求函数结束后,我们就可以cancel掉某个context,从而通知别的routine结束

其它方法例子

请参考:快速掌握 Golang context 包,简单示例

参考

快速掌握 Golang context 包,简单示例
理解Go Context机制
Golang之Context的使用
golang context初探

未读文章

How to correctly use context.Context in Go 1.7
Context API explained
Go Concurrency Patterns: Context

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值