golang: context的应用

一,什么是context?

1,context是什么?

context是 goroutine(协程) 的上下文,包含 goroutine 的运行状态、环境、现场等信息。
context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。
 
2,context的作用:
Context 只有两个简单的功能:
跨 API 或在进程间 1)携带键值对、
2)传递取消信号(主动取消、时限/超时自动取消) !!!
 
3,为什么要使用context?

context.Context 类型的值可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。
最重要的是它是并发安全的。
与它协作的 API 都可以由外部控制执行“取消”操作,例如:取消一个 HTTP 请求的执行。

在Go 里,我们不能直接杀死协程,协程的关闭一般会用 channel+select 方式来控制。
但是在某些场景下,例如处理一个请求衍生了很多协程,这些协程之间是相互关联的:
需要共享一些全局变量、有共同的 deadline 等,而且可以同时被关闭。
再用 channel+select 就会比较麻烦,这时就可以通过 context 来实现。

简单的说context 用来解决 goroutine 之间退出通知、 元数据传递的功能。

二,context的应用

1,创建根context的两个方法:

var (
 background = new(emptyCtx)
 todo       = new(emptyCtx)
)

func Background() Context {
 return background
}

func TODO() Context {
 return todo
}

 

2,创建子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, val interface{}) Context

 

3,例子:

两层context

rootCtx := context.Background()
childCtx := context.WithValue(rootCtx, "request_Id", "seekload")

三层context

rootCtx := context.Background()
childCtx := context.WithValue(rootCtx, "request_Id", "seekload")
childOfChildCtx, cancelFunc := context.WithCancel(childCtx)

四层context

rootCtx := context.Background()
childCtx1 := context.WithValue(rootCtx, "request_Id", "seekload")
childCtx2, cancelFunc := context.WithCancel(childCtx1)
childCtx3 := context.WithValue(rootCtx, "user_Id", "user_100")

三,context的使用例子:

1,context.WithCancel() 用于取消信号

func main() {
    ctx := context.Background() //创建根context
    cancelCtx, cancelFunc := context.WithCancel(ctx) //基于根创建第二层context对象,传入协程中
    go task(cancelCtx)
    time.Sleep(time.Second * 3)
    cancelFunc()                        // 可以在父级对创建的子context发起取消 context(即结束协程)
    time.Sleep(time.Second * 1)         // 延时等待协程退出
    fmt.Println("number of goroutine: ",runtime.NumGoroutine()) // 协程数量
}

func task(ctx context.Context) {
    i := 1
    for {
        select {
        case <-ctx.Done(): // 接收取消信号
            fmt.Println("Gracefully exit")
            fmt.Println(ctx.Err()) // 取消原因
            return
        default:
            fmt.Println(i)
            time.Sleep(time.Second * 1)
            i++
        }
    }
}

2, context.WithValue() 可以在 goroutine 之间传递一些数据

func main() {
    helloWorldHandler := http.HandlerFunc(HelloWorld)
    http.Handle("/hello", inejctRequestId(helloWorldHandler))
    http.ListenAndServe(":8080", nil)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
    requestId := ""
    if m := r.Context().Value("requestId"); m != nil {
        if value, ok := m.(string); ok {
            requestId = value
        }
    }
    w.Header().Add("requestId", requestId)
    w.Write([]byte("Hello, world"))
}

func inejctRequestId(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestId := uuid.New().String()
        ctx := context.WithValue(r.Context(), "requestId", requestId)
        req := r.WithContext(ctx)
        next.ServeHTTP(w, req)
    })
}

3,context.WithTimeout() 可以设置一个超时时间,过期之后 channel done 会自动关闭,context 会被取消;
   超时之前可以调用取消函数手动取消 context

func main() {
    ctx := context.Background()
    cancelCtx, cancel := context.WithTimeout(ctx, time.Second*3)
    defer cancel()
    go task(cancelCtx)
    time.Sleep(time.Second * 4)
}

func task(ctx context.Context) {
    i := 1
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Gracefully exit")
            fmt.Println(ctx.Err())
            return
        default:
            fmt.Println(i)
            time.Sleep(time.Second * 1)
            i++
        }
    }
}

4,  context.WithDeadline() 设置一个将来的时间点作为截止时间,时间到了之后,channel done 会自动关闭,context 会被取消;
    还未到截止时间可以调用取消函数手动取消 context

func main() {
    ctx := context.Background()
    cancelCtx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3))
    defer cancel()
    go task(cancelCtx)
    time.Sleep(time.Second * 4)   // 延时,等待 task() 正常退出
}

func task(ctx context.Context) {
    i := 1
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Gracefully exit")
            fmt.Println(ctx.Err())
            return
        default:
            fmt.Println(i)
            time.Sleep(time.Second * 1)
            i++
        }
    }
}

四,参考来源

参考: https://www.jianshu.com/p/d4c0cb65374f

posted @   刘宏缔的架构森林  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2023-02-09 flutter:使用listview之二:下拉刷新(flutter 3.7.0)
2023-02-09 macos安装uni-app开发环境(hbuilderx 3.6.18 / macos12.4)
点击右上角即可分享
微信分享提示