Go Context

  Context通常被译作上下文,它是一个比较抽象的概念。在讨论链式调用技术时也经常会提到上下文。一般理解为程序单元的一个运行状态、现场、快照,而翻译中上下又很好地诠释了其本质,上下则是存在上下层的传递,上会把内容传递给下。在Go语言中,程序单元也就指的是Goroutine。

在Go语言中控制并发有两种经典的方式,一种是WaitGroup,另外一种就是Context。

Context业务场景

每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,在处理这个Request的goroutine中,可能需要在当前gorutine继续开启多个新的Goroutine来获取数据与逻辑处理(例如访问数据库、RPC服务等),即一个请求Request,会需要多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。

初识Context

上面说的这种场景是存在的,比如一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,它就是goroutine的上下文。

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("监控退出,停止了...")
                return
            default:
                fmt.Println("goroutine监控中...")
                time.Sleep(2 * time.Second)
            }
        }
    }(ctx)

    time.Sleep(10 * time.Second)
    fmt.Println("可以了,通知监控停止")
    cancel()
    //为了检测监控过是否停止,如果没有监控输出,就表示停止了
    time.Sleep(5 * time.Second)

}

Context with用法

context.WithValue()  设置值传递

context.WithCancel()  设置关闭Groutine

context.WithTimeout()  设置超时时间

context.WithDeadine()   设置绝对过期时间

 

context.TODO()  返回一个非nil的空Context,当不清楚使用哪种Context或尚不可用时,应该使用context.TODO 

context.Backgroup()  返回一个非nil的空Context,也是一个根Context(top-level Context)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}

Context控制多个goroutine

使用Context控制一个goroutine的例子如上,非常简单,下面我们看看控制多个goroutine的例子,其实也比较简单。

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或者衍生的子Context都会收到通知,这时就可以进行清理操作了,最终释放goroutine,这就优雅的解决了goroutine启动后不可控的问题。

Context传递消息

package main

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

func worker(ctx context.Context, wg sync.WaitGroup) {
    traceCode, ok := ctx.Value("TRACE_CODE").(string)
    if ok {
        fmt.Printf("trace_code:%s\n", traceCode)
    }

    for {
        fmt.Printf("worker, trace_code:%s\n", traceCode)
        select {
        case <- ctx.Done():
            return
        default:
            time.Sleep(time.Second)
        }
    }
    wg.Done()

}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    ctx = context.WithValue(ctx, "TRACE_CODE", "12345678")
    var waitGroup sync.WaitGroup

    go worker(ctx, waitGroup)
    time.Sleep(3 * time.Second)
    cancel()

    waitGroup.Wait()
}

 

 

 

转载至: 

http://www.flysnow.org/2017/05/12/go-in-action-go-context.html

https://studygolang.com/articles/13343

 

posted @ 2018-08-18 11:48  Vincen_shen  阅读(343)  评论(0编辑  收藏  举报