golang context 操作

context 描述
go 1.7 版本之后加入一个新的标准库context 它定义了Context 类型 ,专门用来简化对于处理单个请求的多个goroutine之间与请求域的数据、
取消信号、截止时间等相关操作,这些操作可能涉及到多个API调用。
 
对服务器传入的请求应该创建上下文,而对服务器的传出调用的应该接收上下文,它们之间的函数调用链必须传递上下文,或者可以使用
WithCancel、WithDeadline 、WithTimeout、WithValue 创建的派生上下文,当一个上下文被取消时,它派生的所有上下文也被取消。
 

background 和 todo 本质上都是emptyCtx 结构类型,是一个不可取消,没有设置截止时间, 没有携带任何值的Context
 
context 使用场景
在go http 包的server中 每一个请求在都有一个对应的goroutine去处理,请求处理函数通常会启动额外的goroutine用来访问后端服务例如数据库和RPC服务
用来处理一个请求的goroutine通常需要访问一些请求特定的数据,例如终端用户的身份认证信息,验证相关的token、请求的截止时间
当一个请求取消或者超时,所有用来处理该请求的goroutine 都应该迅速退出,然后系统才会释放这些goroutine占用资源
 
Background()TODO()
 
go 内置两个函数: Background() 和 TODO()这两个函数分别返回一个实现了 Context 接口 的background 和todo 我们代码中最开始都是以两个内置的上下文
对象作为最顶层的partent context 衍生出更多的子上下文对象

Background 主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context 也就是根Context

TODO() 它目前还不知道具体使用场景,如果我们不知道该使用什么context 的时候,可以使用这个
 
没有context 功能时实现goroutine 通知退出操作 
1. 通过变量方式通知的
复制代码
package main
var wg sync.WaitGroup

var notify bool

func demo(){
    var i int
    defer wg.Done()
    
    for {
        i++
        fmt.Printf("判断的方式 执行%v次\n",i)
        time.Sleep(time.Millisecond * 500)
        if notify{
            break
        }
    }
}
// 使用判断参数退出子goroutine

func main(){
    wg.Add(1)
    go demo()
    time.Sleep(time.Second * 3 )
    notify = true
    fmt.Println("判断方式退出子goroutine")
wg.wait() }
复制代码

2. 通过channel 方式通知子goroutine 退出

复制代码
package main
var exitChan chan bool 
var wg sync.WaitGroup
func demo1(){
    defer wg.Done()
    var i int
    // FORLOOP 跳出指定循环体
    EXITFOR:
    for {
        i++
        fmt.Printf("channel方法, 执行%v次\n",i)
        time.Sleep(time.Millisecond * 500 )
        select {
        case <- exitChan:
            break EXITFOR
        default:
        }
    }
}

func main(){
    exitChan = make(chan bool,1)
    wg.Add(1)
    go demo1()
    time.Sleep(time.Second * 3 )
    exitChan <- true
    fmt.Println("channel方法退出子goroutine")
    wg.Wait()
}
复制代码

以上的使用方法比较单一,如果涉及到嵌套的子goroutine 时就无法通知嵌套里面的子goroutine 这时就可以利用context 方法了

context.WithCancel

复制代码
package main
import (
    "context"
    "fmt"
    "sync"
    "time"
)


var wg sync.WaitGroup

// context 方法退出子goroutine
func woker01(ctx context.Context){
    defer wg.Done()
    go woker02(ctx)
    var i int
    // FORLOOP 跳出指定循环体
    EXITFOR:
    for {
        i++
        fmt.Printf("woker02 context方法, 执行%v次\n",i)
        time.Sleep(time.Millisecond * 500 )
        select {
        case <- ctx.Done():
            break EXITFOR
        default:
        }
    }
}

func woker01(ctx context.Context){
    defer wg.Done()
    var i int
    // FORLOOP 跳出指定循环体
    EXITFOR:
    for {
        i++
        fmt.Printf("woker01 context方法, 执行%v次\n",i)
        time.Sleep(time.Millisecond * 500 )
        select {
        case <- ctx.Done():
            break EXITFOR
        default:
        }
    }
}

func main(){
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(1)
    go woker02(ctx)
    time.Sleep(time.Second * 3 )
    fmt.Println("context 退出子goroutine")
    cancel()
    wg.wait()
}
复制代码

context WithDeadline

尽管ctx会过期,但是在任何情况下调用它的cancel 函数都是很好的实践如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间
复制代码
package main

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

/*
context WithDeadline
*/

var wg sync.WaitGroup
func contextDeadline(){
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(),d)

    defer cancel()
    select{
    case <- time.After(1 * time.Second):
        fmt.Println("zhangsan")
    case <- ctx.Done():
        fmt.Println(ctx.Err())
    
    }
}

func main(){
    contextDeadline()

}
复制代码
 
context.WithTimeout   和 context.WithDeadline 类似都是超时退出
复制代码
package main
import (
    "context"
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup
func woker(ctx context.Context){
exitfor:
    for {
        fmt.Println("connect mysql databases.....")
        time.Sleep(time.Millisecond * 10)
        select{
        case <-ctx.Done():
            break exitfor
        default:

        }
    }
    fmt.Println("woker done!")
    wg.Done()

}

func contextTimeout(){
    ctx, cancel :=context.WithTimeout(context.Background(),time.Millisecond * 50 )
    wg.Add(1)
    go woker(ctx)
    time.Sleep(time.Second * 5)
    cancel()
    wg.Wait()
    fmt.Println("runnning close")
}
func main(){
    contextTimeout()
}
复制代码
 
context.WithValue
仅对API和进程之间传递请求域的数据使用上下文值,而不是使用它传递可选参数,所提供的键必须可比较的并且不应该是string 类型或任何其他类型,以免使用上下文在包之间发生冲突。
WithValue 的用户应该为键定义自己的类型,为了避免在分配给interface{} 时进行分配,上下文键通常具有具体类型struct{} 。或者导出的上下文呢关键变量的静态类型应该是指针或接口
 
复制代码
package main

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

type TraceCode string
var wg sync.WaitGroup

/*
context.WithValue 
*/
func wokerValue(ctx context.Context){
    key := TraceCode("TRACE_CODE") 
    traceCode , ok := ctx.Value( key).(string)
    if !ok{
        fmt.Println("invalid trace code")
    }
Loop:
    for {
        fmt.Printf("woker, trace code:%s\n",traceCode)
        time.Sleep(time.Millisecond * 10)
        select{
        case <- ctx.Done():
            break Loop
        default:

        }
    }
    fmt.Println("workerValue")
    wg.Done()

}

func contextValue(){
    ctx, cancel := context.WithTimeout(context.Background(),time.Millisecond*50)
    ctx = context.WithValue(ctx,TraceCode("TRACE_CODE"),"1233221112332")
    wg.Add(1)
    go wokerValue(ctx)
    time.Sleep(time.Second *5 )
    cancel()
    wg.Wait()
    fmt.Println("context value done!")
}
func main(){

    contextValue()


}
复制代码

 

使用context 的注意事项
1. 推荐以参数的方式显示传递Context
2. 以Context作为参数的函数方法,应该把Context作为第一个参数。
3. 给一个函数方法传递Context的时候,不要传递nil ,如果不知道传递什么,就使用context.TODO()
4. Context的Value相关方法应该传递请求域的必要参数,不应该用于传递可选参数
5. Context是线程安全的,可以放心的在多个goroutine中传递
 
posted @   扛把子修BUG  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示