go-context

context用于控制由一个goroutine (A)开启的其他goroutine(BCD…)。当A需要结束时,希望BCD…也相应地结束。

package main

import (
    "context"
    "fmt"
)
//或者使用go官方解释里面的写法(WithCancel)
func a(ctx context.Context) <-chan int { //ctx作为参数,从父context传到子context
    out := make(chan int)
    n := 1
    go func() {
        for {
            select {
                case <-ctx.Done(): //case是否可以立即执行,即无阻塞。下一个case会阻塞,除非
                //main中取出来使用。所以执行cancel函数,该case可以立即执行。
                    return
                case out <- n:
                    n++
            }
        }
    }()
    return out
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() //在main函数执行结束的时候调用,关闭Done channel,select对应的case执行相应的
    //资源清理操作

    for n := range a(ctx) { //父context中的ctx传递给子context
        fmt.Println(n)
        if n == 5 {
            break
        }
    }
}

Background() 返回空的context,该Context一般由接收请求的第一个Goroutine创建,是与进入请求对应的Context根节点,它不能被取消、没有值、也没有过期时间。它常常作为处理Request的顶层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 main() {
    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                //...
                return
            case ...                
            }
        }
    }(ctx) //熟悉这种写法
}

什么是context?
context用于在goroutine之间传递执行状态,即在一个goroutine中开启另一个goroutine,通过context把当前的状态传递过去。一个request会使用多个goroutine进行处理,这多个goroutine需要共享request的一些信息,且当request超时或者取消的时候,这些个goroutine都需要被结束。
context包就是为多个goroutine之间共享数据,以及多个goroutine的管理。
Context接口是context包的核心,定义如下:

type Context interface{
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Deadline会返回一个超时时间,goroutine获得了超时时间后,例如可以对某些io操作设定超时时间
Done返回一个channel,当Context被撤销或过期时,该信道时关闭的,即它是一个表示Context是否已关闭的信号
当Done信道关闭后,Err方法表明Context被撤的原因
Value可以让goroutine共享一些数据,当然获得数据是协程安全的,但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写要加锁
Context接口没有提供方法设置其值和过期时间,也没有提供方法直接将自身撤销。也就是说,Context不能改变和撤销其自身。
goroutine之间的创建和调用关系是层层调用进行的,更靠顶部的goroutine应有办法主动关闭其下属的goroutine的执行。为了实现这种关系,Context结构也应该像一棵树,叶子节点总是由根节点衍生出来的。
要创建Context树,第一步是得到根节点,context.Background函数的返回值就是根节点:
func Background() Context
该函数返回空的Context,该Context一般由接收请求的第一个goroutine创建,是与进入请求对应的Context根节点,它不能被取消、没有值、也没有过期时间。它常常作为处理request的顶层context存在。
创建子节点context的函数:
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 interface{}, val interface{}) Context
函数都接收一个Context类型的参数parent,并返回一个Context类型的值,这样就层层创建出不同的节点。子节点是从复制父节点得到的,并且根据接收的参数设定子节点的一些状态值,就这就可以将子节点传递给下层的goroutine了。

如何通过Context传递改变后的状态?使用Context的goroutine无法取消某个操作,因为这些goroutine是被某个父goroutine创建的,理应只有父goroutine可以取消操作。在父goroutine中可以通过WithCancel方法获得一个cancel方法,从而获得cancel的权利。
第一个WithCancel函数,它是将父节点复制到子节点,并且返回一个CancelFunc函数类型的变量,该函数类型的定义为:
type CancelFunc func()
调用CancelFunc对象将撤销对应的Context对象,这就是主动撤销Context的方法。在父节点所对应的环境中,通过WithCancel函数不经可以创建子节点的context,同时也获得了该节点context的控制权,一旦执行该函数,则该节点context就结束了,则子节点需要通过类似如下代码来判断是否已经结束,并退出该goroutine:

select {
    case <-ctx.Done():
    //do some clean...
}

WithDeadline函数的作用也差不多,它返回的context类型值同样是parent的副本,但其过期时间由deadline和parent的过期时间共同决定。当parent的过期时间早于传入的deadline时间时,返回的过期时间应与parent相同;父节点过期时,其所有的子孙节点必须同时关闭;反之,返回的过期时间为deadline

WithTimeout函数和WithDeadline类似,只不过它传入的是从现在开始context剩余的生命时长。它们都同样返回了所创建的子context的控制权,一个CancelFunc类型的函数变量。

当顶层的request请求函数结束后,我们就可以cancel某个context,从而层层goroutine根据判断ctx.Done()来结束。

WithValue函数,返回一个parent副本,调用该副本的Value(key)方法将得到val。这样不光根节点原有的值保留了,还在子孙节点中加入了新值。若存在同名key,则会被覆盖。

context包通过构建树形关系的context,来达到上一层goroutine能对传递给下一层goroutine的控制。对于处理一个request,需要采用context来层层控制goroutine,以及传递一些变量来共享。
Context对象的生存周期一般仅为一个请求的处理周期,即针对一个请求创建一个context变量,作为context树结构的根。在请求结束后,撤销此ctx变量,释放资源。
每次创建一个goroutine,要么将原有的context传递给goroutine,要么创建一个子context并传递给goroutine。
context能灵活地存储不同类型、不同数目的值,并且使多个goroutine安全地读写其中的值。
当通过父context对象创建子context对象时,可同时获得子context的一个撤销函数,这样父context对象的创建环境就获得了对子context将要被传递到的goroutine的撤销权。

使用原则:
使用context包需要遵循如下的原则来满足接口的一致性以便于静态分析。
不要把context存在一个结构体中,显式地传入函数。context变量需要作为第一个参数使用,一般命名为ctx。
即使方法允许,也不要传入一个nil的context,如果你不确定要用什么context的时候传入一个context.TODO()。
使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数。
同样的context可以用来传递到不同的goroutine中,context在多个goroutine中是安全的。
在子context被传递到的goroutine中,应该对该子context的信道channel进行监控,一旦该信道被关闭,即上层运行环境撤销了本goroutine的执行,应当主动终止对当前请求信息的处理,释放资源并返回。

WithCancel 初始化一个可以被cancel的context,同时把新的context对象作为child放入parent的children数组中。当parent终止时,child也会收到信号。这个过程叫propagateCancel
WithDeadline 同样初始化一个context,除了实现跟WithCancel同样的功能外,还增加了一个时间变量,一旦当前时间超出这个deadline,那么这个context以及它所有的子孙都会被cancel
WithTimeout 跟WithDeadline类似,如果说WithDeadline是一个绝对时间上的限制,那么WithTimeout是一个相对时间上的限制
WithValue 单纯地给parent增加value,不需要propagateCancel,value可以用来跨进程、跨api传递数据,最好是和某个请求相关的参数,不要传递太多大量数据
关键在于propagateCancel,一旦parent被cancel,就会通过propagateCancel递归地传播给下面的所有子孙。

在Go http包的Server中,每一个请求都有一个对应的goroutine去处理。请求处理函数通常会启动额外的goroutine用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的goroutine通常需要访问一些与请求特定的数据,比如用户的身份验证信息、验证相关的Token、请求的截止时间。 当一个请求被取消或超时时,所有用于处理该请求的goroutine都应该迅速退出,然后系统才能释放这些goroutine占用的资源。

Context包专门用来简化对于处理单个请求的多个goroutine之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用。

Context对象是协程安全的,可以把一个context对象传递给任意个goroutine
Deadline方法允许函数确定它们是否应该开始工作,如果剩下的时间太少,也许这些函数就不值得启动。可以使用Deadline为I/O操作设置截止时间。

Value方法允许context对象携带request的数据,该数据必须是协程安全的???

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值