golang context上下文信息

当需要在多个 goroutine 中传递上下文信息时,可以使用 Context 实现。Context 除了用来传递上下文信息,还可以用于传递终结执行子任务的相关信号,中止多个执行子任务的 goroutine。context 中提供以下接口:
(context包下的接口)

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

Deadline 方法,返回 Context 被取消的时间,也就是完成工作的截止日期;

Done,返回一个 channel,这个 channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 channel;

Err 方法,返回 Context 结束的原因,它只会在 Done 返回的 channel 被关闭时才会返回非空的值,如果 Context 被取消,会返回 Canceled 错误;如果 Context 超时,会返回 DeadlineExceeded 错误。

Value 方法,可用于从 Context 中获取传递的键值信息。

在 Web 请求的处理过程中,一个请求可能启动多个 goroutine 协同工作,这些 goroutine 之间可能需要共享请求的信息,且当请求被取消或者执行超时时,该请求对应的所有 goroutine 都需要快速结束,释放资源。Context 就是为了解决上述场景而开发的,我们通过下面一个例子来演示:

package main
import (
    "context"
    "fmt"
    "time"
)
const DB_ADDRESS  = "db_address"
const CALCULATE_VALUE  = "calculate_value"
func readDB(ctx context.Context, cost time.Duration)  {
    fmt.Println("db address is", ctx.Value(DB_ADDRESS))
    select {
    case <- time.After(cost): //  模拟数据库读取
        fmt.Println("read data from db")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // 任务取消的原因
        // 一些清理工作
    }
}
func calculate(ctx context.Context, cost time.Duration)  {
    fmt.Println("calculate value is", ctx.Value(CALCULATE_VALUE))
    select {
    case <- time.After(cost): //  模拟数据计算
        fmt.Println("calculate finish")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // 任务取消的原因
        // 一些清理工作   比如关闭goroutine
    }
}
func main()  {
    ctx := context.Background(); // 创建一个空的上下文
    // 添加上下文信息
    ctx = context.WithValue(ctx, DB_ADDRESS, "localhost:10086")
    ctx = context.WithValue(ctx, CALCULATE_VALUE, 1234)
    // 设定子 Context 2s 后执行超时返回
    ctx, cancel := context.WithTimeout(ctx, time.Second*2)   //会返回一个取消函数
    defer cancel()
    // 设定执行时间为 4 s
    go readDB(ctx, time.Second*4)
    go calculate(ctx, time.Second*4)

    // 充分执行
    time.Sleep(time.Second * 5)
}

用context作为协成的参数

在上述例子中,我们模拟了一个请求中同时进行数据库访问和逻辑计算的操作,在请求执行超时时,及时关闭尚未执行结束 goroutine。我们首先通过 context.WithValue 方法为 context 添加上下文信息,Context 在多个 goroutine 中是并发安全的,可以安全地在多个 goroutine 中对 Context 中的上下文数据进行读取。接着使用 context.WithTimeout 方法设定了 Context 的超时时间为 2s,并传递给 readDB 和 calculate 两个 goroutine 执行子任务。在 readDB 和 calculate 方法中,使用 select 语句对 Context 的 Done 通道进行监控。由于我们设定了子 Context 将在 2s 之后超时,所以它将在 2s 之后关闭 Done 通道;然而预设的子任务执行时间为 4s,对应的 case 语句尚未返回,执行被取消,进入到清理工作的 case 语句中,结束掉当前的 goroutine 所执行的任务。预期的输出结果如下:


这就是超时报错
如果你想看看取消不错,可以修改主goroutine的slesp为1秒,然后再defer语句上添加defer fmt.Println(ctx.Err())

使用 Context,能够有效地在一组 goroutine 中传递共享值、取消信号、deadline 等信息,及时关闭不需要的 goroutine。

你可能感兴趣的:(golang context上下文信息)