关于golang:Go语言并发控制神器之Context

54次阅读

共计 3407 个字符,预计需要花费 9 分钟才能阅读完成。

协程如何退出

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

package main
import (
    "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 main
import (
    "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 main
import (
    "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 是多协程平安的,能够在多个协程中应用

正文完
 0