golang context

golang的context是一个用于在程序中跨API边界或进程传递请求范围内的数据、取消信号、超时或截止时间的工具。它是Go并发编程中最重要的工具之一。

context是一个接口类型,定义了四个方法:Deadline,Done,Err和Value。这些方法分别用于获取context的截止时间,完成信号,错误原因和键值对数据。

context可以通过调用context包中的函数来创建和取消。最常见的两种创建方式是:

context.Background:返回一个空的根context,通常用于main函数或测试代码中。

context.TODO:返回一个空的临时context,通常用于不确定要使用哪种context时。

例如:

func main() {

    // 创建一个空的根context

    ctx := context.Background()

    // 创建一个空的临时context

    ctx = context.TODO()

}

context应该在服务器接收到请求时创建,在向其他服务器发出请求时传递。这样可以保证请求在整个链路上有一致性和可控性。

例如:

func handleRequest(w http.ResponseWriter, req *http.Request) {

    // 从请求中获取参数

    name := req.FormValue("name")

    // 创建一个带有键值对数据的子context

    ctx := context.WithValue(req.Context(), "name", name)

    // 调用其他函数,并传递子context

    result, err := doSomething(ctx)

    if err != nil {

        w.WriteHeader(http.StatusInternalServerError)

        return

    }

    w.Write(result)

}

context可以通过WithCancel,WithDeadline,WithTimeout或WithValue函数来派生子context,并且当父context被取消时,所有子context也会被取消。这些函数分别用于创建带有取消函数,固定截止时间,动态超时时间或键值对数据的子context。

例如:

func main() {

    // 创建一个空的根context

    ctx := context.Background()

    

    // 创建一个带有取消函数的子context

    cancelCtx, cancelFunc := context.WithCancel(ctx)

    

    // 创建一个带有固定截止时间(2023年3月10日)的子context

    deadlineCtx, _ := context.WithDeadline(ctx, time.Date(2023, 3, 10, 0, 0 ,0 ,0 ,time.UTC))

    

     // 创建一个带有动态超时时间(10秒)的子context

     timeoutCtx, _ := context.WithTimeout(ctx, time.Second * 10)



     // 创建一个带有键值对数据("foo":"bar") 的子context 

     valueCtx := context.WithValue(ctx,"foo","bar")

}

注意:当不再需要使用取消函数或派生出来的子context时 ,应该及时释放它们以避免内存泄漏 。

例如:

func main() { 

// 创建一个空 的 根context 

ctx := context.Background () 



// 创建一个带有取消函数的子context 

cancelCtx , cancelFunc := context.WithCancel (ctx ) 



// 在 函数 结束 前 调用 取消 函数 

defer cancelFunc () 



// ... 其他 逻辑 ... 

} 

context 不应该存储在结构体中 而应该作为函数参数显式传递 。这样可以避免context被滥用或泄露,也可以保持代码的清晰和一致。

例如:

// 不推荐的做法:将context存储在结构体中

type Service struct {

    ctx context.Context

}



func (s *Service) DoSomething() {

    // 使用结构体中的context

    result, err := doSomething(s.ctx)

    // ...

}



// 推荐的做法:将context作为函数参数传递

type Service struct {

    // ...

}



func (s *Service) DoSomething(ctx context.Context) {

    // 使用函数参数中的context

    result, err := doSomething(ctx)

    // ...

}

context的应用场景有很多,比如:

在http server中,每个请求都会创建一个context,用来存储请求相关的信息,如客户端IP、用户ID等。同时,context也可以用来控制请求的生命周期,比如在请求超时或被取消时,通知子goroutine结束。

在数据库操作中,context可以用来设置查询的超时时间或截止时间,以避免长时间占用数据库资源。

在分布式系统中,context可以用来传递跟踪ID或元数据等信息,在不同的服务之间建立关联。

下面是一些go语言context的应用场景的例子代码:

http server中使用context控制请求生命周期

package main



import (

 "context"

 "fmt"

 "log"

 "net/http"

 "time"

)



func main() {

 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

  ctx := r.Context()

  log.Printf("handler started")

  defer log.Printf("handler ended")



  select {

  case <-time.After(5 * time.Second):

   fmt.Fprintln(w, "hello")

  case <-ctx.Done():

   err := ctx.Err()

   log.Print(err)

   http.Error(w, err.Error(), http.StatusInternalServerError)

  }

 })



 http.ListenAndServe(":8080", nil)

}

数据库操作中使用context设置查询超时时间

package main



import (

    "context"

    "database/sql"

    "fmt"

    _ "github.com/go-sql-driver/mysql"

    "log"

    "time"

)



func main() {

    db, err := sql.Open("mysql", "root:123456@tcp(localhost:3306)/test")

    if err != nil {

        log.Fatal(err)

    }

    defer db.Close()



    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

    defer cancel()



    var name string

    err = db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", 1).Scan(&name)

    if err != nil {

        log.Fatal(err)

    }

    

    fmt.Println(name)

}

分布式系统中使用context传递跟踪ID

package main



import (

 "context"

 "fmt"



 opentracing "github.com/opentracing/opentracing-go"

)



func main() {

 tracer := opentracing.GlobalTracer()

 ctx := context.Background()



 parentSpan := tracer.StartSpan("parent")

 defer parentSpan.Finish()



 ctx = opentracing.ContextWithSpan(ctx, parentSpan)



 childSpan := tracer.StartSpan(

     "child",

     opentracing.ChildOf(parentSpan.Context()),

   )

 defer childSpan.Finish()



 fmt.Println(ctx.Value(opentracing.SpanContextKey))

}

context 在 goroutine 中的应用案例有很多:

取消 goroutine:

当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。我们可以使用 context.WithCancel 或 context.WithTimeout 函数来创建一个可取消的子 context,并传递给每个 goroutine。当主函数调用 cancel 函数或者超时发生时,所有的子 goroutine 都会收到 ctx.Done() 信号,从而结束运行。

超时时间:

当我们需要对某个操作设置一个执行时间上限时,我们可以使用 context.WithTimeout 函数来创建一个带有超时时间的子 context,并传递给需要执行该操作的 goroutine。如果超过了设定的时间,该子 context 就会被取消,goroutine 就会收到 ctx.Done() 信号,从而停止执行。

运行截止时间:

类似于超时时间,我们也可以使用 context.WithDeadline 函数来创建一个带有运行截止时间的子 context,并传递给需要执行某个操作的 goroutine。如果到达了截止时间,该子 context 就会被取消,goroutine 就会收到 ctx.Done() 信号,从而停止执行。

k-v 存储:

context 包提供了一种机制,在 Context 中保存和获取键值对。这在多个 goroutine 之间传递和控制一些与请求相关的数据非常有用。例如,在 HTTP 请求中,我们可以使用 context.WithValue 函数来存储和获取用户身份认证信息、验证相关的 token、请求 ID 等数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值