协程如何退出

一个协程启动后,个别是代码执行结束,主动退出,然而如果须要提前终止怎么办呢?
一个方法是定义一个全局变量,协程中通过查看这个变量的变动来决定是否退出。这种方法须要加锁来保障并发平安,说到这里,有没有想的什么解决方案?
select + channel 来实现:

package mainimport (    "fmt"    "sync"    "time")func main() {    var wg sync.WaitGroup    stopWk := make(chan bool)    wg.Add(1)    go func() {        defer wg.Done()        worker(stopWk)    }()    time.Sleep(3*time.Second) //工作3秒    stopWk <- true //3秒后收回进行指令    wg.Wait()}func worker(stopWk chan bool){    for {        select {        case <- stopWk:            fmt.Println("上班咯~~~")            return        default:            fmt.Println("认真摸鱼中,请勿打扰...")        }        time.Sleep(1*time.Second)    }}

运行后果:

认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...上班咯~~~

能够看到,每秒打印一次“认真摸鱼中,请勿打扰...”,3秒后收回进行指令,程序进入 “上班咯~~~”。

Context 初体验

下面咱们应用 select+channel 来实现了协程的终止,然而如果咱们想要同时勾销多个协程怎么办呢?如果须要定时勾销又怎么办呢?
此时,Context 就须要退场了,它能够跟踪每个协程,咱们重写下面的示例:

package mainimport (    "context"    "fmt"    "sync"    "time")func main() {    var wg sync.WaitGroup    ctx, stop := context.WithCancel(context.Background())    wg.Add(1)    go func() {        defer wg.Done()        worker(ctx)    }()    time.Sleep(3*time.Second) //工作3秒    stop() //3秒后收回进行指令    wg.Wait()}func worker(ctx context.Context){    for {        select {        case <- ctx.Done():            fmt.Println("上班咯~~~")            return        default:            fmt.Println("认真摸鱼中,请勿打扰...")        }        time.Sleep(1*time.Second)    }}

运行后果:

认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...上班咯~~~

Context 介绍

Context 是并发平安的,它是一个接口,能够手动、定时、超时收回勾销信号、传值等性能,次要是用于管制多个协程之间的合作、勾销操作。

Context 接口有四个办法:

type Context interface {   Deadline() (deadline time.Time, ok bool)   Done() <-chan struct{}   Err() error   Value(key interface{}) interface{}}
  • Deadline 办法:能够获取设置的截止工夫,返回值 deadline 是截止工夫,到了这个工夫,Context 会主动发动勾销申请,返回值 ok 示意是否设置了截止工夫。
  • Done 办法:返回一个只读的 channel ,类型为 struct{}。如果这个 chan 能够读取,阐明曾经收回了勾销信号,能够做清理操作,而后退出协程,开释资源。
  • Err 办法:返回Context 被勾销的起因。
  • Value 办法:获取 Context 上绑定的值,是一个键值对,通过 key 来获取对应的值。
最罕用的是 Done 办法,在 Context 勾销的时候,会敞开这个只读的 Channel,相当于收回了勾销信号。

Context 树

咱们并不需要本人去实现 Context 接口,Go 语言提供了函数来生成不同的 Context,通过这些函数能够生成一颗 Context 树,这样 Context 就能够关联起来,父级 Context 收回勾销信号,子级 Context 也会收回,这样就能够管制不同层级的协程退出。

生成根节点

  1. emptyCtx是一个int类型的变量,但实现了context的接口。emptyCtx没有超时工夫,不能取消,也不能存储任何额定信息,所以emptyCtx用来作为 context 树的根节点。
  2. 然而咱们个别不间接应用emptyCtx,而是应用由emptyCtx实例化的两个变量(background 、todo),别离通过调用BackgroundTODO办法失去,但这两个 context 在实现上是一样的。
Background和TODO办法区别:
BackgroundTODO只是用于不同场景下:Background通常被用于主函数、初始化以及测试中,作为一个顶层的context,也就是说个别咱们创立的context都是基于Background;而TODO是在不确定应用什么context的时候才会应用。

生成树的函数

  1. 能够通过 context。Background() 获取一个根节点 Context。
  2. 有了根节点后,再应用以下四个函数来生成 Context 树:
  3. WithCancel(parent Context):生成一个可勾销的 Context。
  4. WithDeadline(parent Context, d time.Time):生成一个可定时勾销的 Context,参数 d 为定时勾销的具体工夫。
  5. WithTimeout(parent Context, timeout time.Duration):生成一个可超时勾销的 Context,参数 timeout 用于设置多久后勾销
  6. WithValue(parent Context, key, val interface{}):生成一个可携带 key-value 键值对的 Context。

Context 勾销多个协程

如果一个 Context 有子 Context,在该 Context 勾销时,其下的所有子 Context 都会被勾销。

Context 传值

Context 不仅能够收回勾销信号,还能够传值,能够把它存储的值提供其余协程应用。

示例:

package mainimport (    "context"    "fmt"    "sync"    "time")func main() {    var wg sync.WaitGroup    ctx, stop := context.WithCancel(context.Background())    valCtx := context.WithValue(ctx, "position","gopher")    wg.Add(2)    go func() {        defer wg.Done()        worker(valCtx, "打工人1")    }()    go func() {        defer wg.Done()        worker(valCtx, "打工人2")    }()    time.Sleep(3*time.Second) //工作3秒    stop() //3秒后收回进行指令    wg.Wait()}func worker(valCtx context.Context, name string){    for {        select {        case <- valCtx.Done():            fmt.Println("上班咯~~~")            return        default:            position := valCtx.Value("position")            fmt.Println(name,position, "认真摸鱼中,请勿打扰...")        }        time.Sleep(1*time.Second)    }}

运行后果:

打工人2 gopher 认真摸鱼中,请勿打扰...打工人1 gopher 认真摸鱼中,请勿打扰...打工人1 gopher 认真摸鱼中,请勿打扰...打工人2 gopher 认真摸鱼中,请勿打扰...打工人2 gopher 认真摸鱼中,请勿打扰...打工人1 gopher 认真摸鱼中,请勿打扰...上班咯~~~上班咯~~~

Context 应用准则

  • Context 不要放在构造体中,须要以参数形式传递
  • Context 作为函数参数时,要放在第一位,作为第一个参数
  • 应用 context。Background 函数生成根节点的 Context
  • Context 要传值必要的值,不要什么都传
  • Context 是多协程平安的,能够在多个协程中应用