package main
import (
"context"
"fmt"
)
type keyOne string
type keyTwo string
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, keyOne("one"), "valueOne")
ctx = context.WithValue(ctx, keyTwo("one"), "valueTwo")
fmt.Println(ctx.Value(keyOne("one")).(string))
fmt.Println(ctx.Value(keyTwo("one")).(string))
}
上下文是 GO 提供的包。让我们首先了解一些已经存在的问题,以及哪个上下文包试图解决。
If you notice all the above problems are quite applicable to HTTP requests and but none the less these problems are also applicable to many different areas too.
如果您注意到上述所有问题都非常适用于HTTP请求,但这些问题也适用于许多不同的领域。
For a web HTTP request, it needs to be canceled when the client has disconnected, or the request has to be finished within a specified timeout and also requests scope values such as request_id needs to be available to all downstream functions.
对于 Web HTTP 请求,当客户端断开连接时,需要取消该请求,或者请求必须在指定的超时内完成,并且请求范围值(如request_id)需要可供所有下游函数使用。
The core of the understanding context is knowing the Context interface
理解上下文的核心是了解上下文接口
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
// 上下文包含截止时间、取消信号和其他值API 边界。
// Context's methods may be called by multiple goroutines simultaneously.
// 上下文的方法可以由多个 goroutine 同时调用。
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
// 截止时间返回代表此上下文完成工作的时间
// 应该取消。截止日期返回 ok==false,当没有截止日期
// 设置。对 Deadline 的连续调用将返回相同的结果。
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// The close of the Done channel may happen asynchronously,
// after the cancel function returns.
// Done 返回在代表此节点完成工作时关闭的通道
// 上下文应取消。如果此上下文可以
// 永远不会被取消。对 Done 的连续调用返回相同的值。
// 完成通道的关闭可能会异步发生,
// 在取消函数返回后。
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
// WithCancel 安排在调用取消时关闭 Done;
// WithDeadline安排在截止日期前关闭完成
// 过期;WithTimeout 安排在超时时关闭 Done
// 流逝。
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancellation.
Done() <-chan struct{}
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stored using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
}
context package function Background() returns a empty Context which implements the Context interface
上下文包 Background() 返回一个空的 Context,该上下文实现 Context 接口
Then what is the use context.Background(). context.Background() serves as the root of all context which will be derived from it. It will be more clear as we go along
其次context.Background()有什么用,它将作为从中派生的所有上下文的根。随着我们的前进,它将更加清晰。
The above two methods describe a way of creating new contexts. More context can be derived from these contexts. This is where context tree comes into the picture
上述两种方法描述了一种创建新上下文的方法。可以从这些上下文中派生出更多上下文。这就是上下文树进入画面的地方
Before understanding Context Tree please make sure that it is implicitly created in the background when using context. You will find no mention of in go context package itself.
在理解上下文树之前,请确保在使用上下文时在后台隐式创建它。你会发现在go上下文包本身中没有提到。
Whenever you use context, then the empty Context got from context.Background() is the root of all context. context.ToDo() also acts like root context but as mentioned above it is more like a context placeholder for future use. This empty context has no functionality at all and we can add functionality by deriving a new context from this. Basically a new context is created by wrapping an already existing immutable context and adding additional information. Let's see some example of a context tree which gets created
每当您使用上下文时,空的上下文都会从上下文中获取。Background() 是所有上下文的根。context.ToDo() 也像根上下文一样,但如上所述,它更像是供将来使用的上下文占位符。这个空上下文根本没有功能,我们可以通过从中派生一个新的上下文来添加功能。基本上,通过包装已经存在的不可变上下文并添加其他信息来创建新上下文。让我们看一些创建的上下文树的示例
Two level tree
package main
import (
"context"
"fmt"
)
func main() {
// 一级的context
rootCtx := context.Background()
fmt.Println(rootCtx)
// 二级的context
childCtx := context.WithValue(rootCtx, "msgId", "someMsgId")
fmt.Println(childCtx)
fmt.Println(childCtx.Value("msgId"))
}
In above
Three level tree
package main
import (
"context"
"fmt"
)
func main() {
// 一级的context
rootCtx := context.Background()
fmt.Println(rootCtx)
// 二级的context
childCtx := context.WithValue(rootCtx, "msgId", "someMsgId")
// 三级的context
childOfChildCtx, cancelFunc := context.WithCancel(childCtx)
fmt.Println(childOfChildCtx)
fmt.Println(cancelFunc)
// 一级比一级功能更全
}
In above
Multi-level tree
package main
import (
"context"
"fmt"
)
func main() {
// 一级 context
rootCtx := context.Background()
// 二级 context,继承一级
childCtx2 := context.WithValue(rootCtx, "msgId", "someMsgId")
// 三级级 context,继承二级
childCtx3, cancelFunc := context.WithCancel(childCtx2)
fmt.Println(childCtx3)
fmt.Println(cancelFunc)
// 二级 context
childCtx4 := context.WithValue(rootCtx, "user_id", "some_user_id")
fmt.Println(childCtx4)
fmt.Println(childCtx4.Value("user_id"))
}
A derived(派生) context is can be created in 4 ways
Let's understand each of the above in details
Used for passing request-scoped values. The complete signature of the function is
withValue(parent Context, key, val interface{}) (ctx Context)
It takes in a parent context, key, value and returns a derived context This derived context has key associated with the value. Here the parent context can be either context.Background() or any other context. Further, any context which is derived from this context will have this value.
#Root Context
ctxRoot := context.Background() - #Root context
#Below ctxChild has acess to only one pair {"a":"x"}
ctxChild := context.WithValue(ctxRoot, "a", "x")
#Below ctxChildofChild has access to both pairs {"a":"x", "b":"y"} as it is derived from ctxChild
ctxChildofChild := context.WithValue(ctxChild, "b", "y")
Example1 http请求链路注入场景
package main
import (
"context"
"github.com/google/uuid"
"net/http"
)
func main() {
helloWorldHandler := http.HandlerFunc(HelloWorld)
http.Handle("/welcome", inejctMsgID(helloWorldHandler))
http.ListenAndServe(":8080", nil)
}
//HelloWorld hellow world handler
func HelloWorld(w http.ResponseWriter, r *http.Request) {
msgID := ""
if m := r.Context().Value("msgId"); m != nil {
if value, ok := m.(string); ok {
msgID = value
}
}
w.Header().Add("msgId", msgID)
w.Write([]byte("Hello, world"))
}
func inejctMsgID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
msgID := uuid.New().String()
ctx := context.WithValue(r.Context(), "msgId", msgID)
req := r.WithContext(ctx)
next.ServeHTTP(w, req)
})
}
Example2 执行任务主动退出场景
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
cancelCtx, cancelFunc := context.WithCancel(ctx)
go task(cancelCtx)
time.Sleep(time.Second * 3)
// 手动主动退出
cancelFunc()
time.Sleep(time.Second * 1)
}
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++
}
}
}
Example3 执行任务超时退出场景
package main
import "net/http"
import "log"
import "io/ioutil"
import "context"
import "time"
func main () {
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*80))
defer cancel()
req = req.WithContext(ctx)
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
log.Println(string(out))
}
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
cancelCtx, cancel := context.WithTimeout(ctx, time.Second*3)
//最终退出
defer cancel()
go task1(cancelCtx)
time.Sleep(time.Second * 4)
}
func task1(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++
}
}
}
Example4 执行任务到期退出场景
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
// 到达截止时间退出
cancelCtx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*5))
defer cancel()
go task(cancelCtx)
time.Sleep(time.Second * 6)
}
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://golangbyexample.com/using-context-in-golang-complete-guide/
https://golang.cafe/blog/golang-context-with-timeout-example.html
https://gobyexample.com/context
https://www.sohamkamani.com/golang/context-cancellation-and-values/