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 等数据。