当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > go Context的使用

go Context的使用

2019年02月19日  | 移动技术网IT编程  | 我要评论

控制并发有两种经典的方式,一种是waitgroup,另外一种就是context

waitgroup的使用

  • waitgroup可以用来控制多个goroutine同时完成
    func main() {
    var wg sync.waitgroup
    wg.add(2)
    go func() {
        time.sleep(2*time.second)
        fmt.println("1号完成")
        wg.done()
    }()
    go func() {
        time.sleep(2*time.second)
        fmt.println("2号完成")
        wg.done()
    }()
    wg.wait()
    fmt.println("好了,大家都干完了,放工")
}
以上例子一定要等到两个goroutine同时做完才会全部完成,这种控制并发方式尤其适用于多个goroutine协同做一件事情的时候。

chan通知

  • chan也可以用于控制goroutine,通过chan来控制goroutine是否结束
    func main() {
    stop := make(chan bool)

    go func() {
        for {
            select {
            case <-stop:
                fmt.println("监控退出,停止了...")
                return
            default:
                fmt.println("goroutine监控中...")
                time.sleep(2 * time.second)
            }
        }
    }()

    time.sleep(10 * time.second)
    fmt.println("可以了,通知监控停止")
    stop<- true
    //为了检测监控过是否停止,如果没有监控输出,就表示停止了
    time.sleep(5 * time.second)
}
例子中我们通过select判断stop是否接受到值,如果接受到值就表示可以推出停止了,如果没有接受到,就会执行default里面的监控逻辑,继续监控,直到收到stop的通知

以上控制goroutine的方式在大多数情况下可以满足我们的使用,但是也存在很多局限性,比如有很多goroutiine,并且这些goroutine还衍生了其他goroutine,此时chan就比较困难解决这样的问题了

context

以上问题是存在的,比如一个网络请求request,每个request都需要开启一个goroutine做一些事情。所以我们需要一种可以跟踪goroutine的方案才可以达到控制的目的,go为我们提供了context

func main() {
    ctx, cancel := context.withcancel(context.background())
    go watch(ctx,"【监控1】")
    go watch(ctx,"【监控2】")
    go watch(ctx,"【监控3】")
    time.sleep(10 * time.second)
    fmt.println("可以了,通知监控停止")
    cancel()
    //为了检测监控过是否停止,如果没有监控输出,就表示停止了
    time.sleep(5 * time.second)
}
func watch(ctx context.context, name string) {
    for {
        select {
        case <-ctx.done():
            fmt.println(name,"监控退出,停止了...")
            return
        default:
            fmt.println(name,"goroutine监控中...")
            time.sleep(2 * time.second)
        }
    }
}

例子中启动了3个监控goroutine进行不断的监控,每一个都使用context进行跟踪,当我们使用cancel函数通知取消时候,这3个 goroutine都会被结束。所有基于这个context或者衍生出来的子context都会收到通知,这样就可以进行清理操作最终释放goroutine了

context接口

context是一个接口,具体的内容如下:
~go
type context interface {
deadline() (deadline time.time, ok bool)
done() <-chan struct{}
err() error
value(key interface{}) interface{}
}
~

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

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的意思

  • withcancel函数,传递一个父context作为参数,返回子context,以及一个取消函数用来取消context
  • withdeadline函数,和withcancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消
  • withtimeout和withdeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消context的意思
  • withvalue函数和取消context无关,它是为了生成一个绑定了一个键值对数据的context,这个绑定的数据可以通过context.value方法访问到

context使用原则

  1. 不要把context放在结构体中,要以参数的方式进行传递
  2. 以context作为参数的函数方法,应该把context作为第一个参数,放在第一位
  3. 给一个函数方法传递context的时候,不要传递nil,如果不知道传递什么,就使用context.todo
  4. context的value相关方法应该传递必须的数据,不要什么数据都使用这个传递

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网