Go再学习_3.Go Context

前言

在 Go 1.7 版本之前,context 还是非编制的,它存在于 golang.org/x/net/context 包中。

Context

type Context interface {
    Deadline() (deadline time.Time, ok bool)  // 截止时间
    Done() <-chan struct{}  // cancel后返回,可读
    Err() error // context被cancel的原因
    Value(key interface{}) interface{} // 绑定到Context的值
}

当一个协程(goroutine)开启后,我们是无法强制关闭它的。常见的关闭协程的原因有如下几种:

  • goroutine 自己跑完结束退出
  • 主进程crash退出,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)
}

第三种可以使用,但是不够优雅,好用。在goroutine比较多情况下,难以维护。所以,出现了context:

package main

import (
    "context"
    "fmt"
    "time"
)

func monitor(ctx context.Context, number int)  {
    for {
        select {
        // 其实可以写成 case <- ctx.Done()
        // 这里仅是为了让你看到 Done 返回的内容
        case v :=<- ctx.Done():
            fmt.Printf("监控器%v,接收到通道值为:%v,监控结束。\n", number,v)
            return
        default:
            fmt.Printf("监控器%v,正在监控中...\n", number)
            time.Sleep(2 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    for i :=1 ; i <= 5; i++ {
        go monitor(ctx, i)
    }

    time.Sleep( 1 * time.Second)
    // 关闭所有 goroutine
    cancel()

    // 等待5s,若此时屏幕没有输出 <正在监控中> 就说明所有的goroutine都已经关闭
    time.Sleep( 5 * time.Second)

    fmt.Println("主程序退出!!")
}

同时,它还有几种常用继承衍生:

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

Context 使用注意事项:

  • 通常 Context 都是做为函数的第一个参数进行传递(规范性做法),并且变量名建议统一叫 ctx
  • Context 是线程安全的,可以放心地在多个 goroutine 中使用。
  • 当你把 Context 传递给多个 goroutine 使用时,只要执行一次 cancel 操作,所有的 goroutine 就可以收到 取消的信号
  • 不要把原本可以由函数参数来传递的变量,交给 Context 的 Value 来传递。
  • 当一个函数需要接收一个 Context 时,但是此时你还不知道要传递什么 Context 时,可以先用 context.TODO 来代替,而不要选择传递一个 nil。
  • 当一个 Context 被 cancel 时,继承自该 Context 的所有 子 Context 都会被 cancel。
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页