参考:Golang中 Context包深入浅出
参考:golang中Context的使用场景
一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。
所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,它就是goroutine的上下文。
也就是说面对多个gogroutine可能又开启子gogroutine的情况,我们可以通过context包来控制这些协程的声明周期
获取设置的截止时间,deadline是截止时间,到了这个时间点,Context会自动发起取消请求;
返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
// 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.
Deadline() (deadline time.Time, ok bool)
返回一个只读的chan,类型为struct{}。
我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,否则返回nil
// 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.
// Done is provided for use in select statements:
Done() <-chan struct{
}
如果还没有被关闭,返回nil。否则返回取消的错误原因。
// 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
返回Context上绑定的数据,通过key来获取对应的value
Value(key interface{
}) interface{
}
主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Contex
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context
不确定使用场景和还没需要用到Context入参的时候使用
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context
返回一个子Context和一个CanceFunc函数,当调用CancelFunc或者父Context结束的时候,这个Context也会关闭
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
返回一个子Context。传入的时间d为最后截至时间,然后父Context如果结束的话,还没到截止时间,子Context也会结束。
// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
底层也是调用WithDeadline,在多少时间之后超时关闭。
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
// defer cancel() // releases resources if slowOperation completes before timeout elapses
// return slowOperation(ctx)
// }
func WithTimeout(parent Context, timeout time.Duration)
生成一个带key-value值得Context上下文。
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
func WithValue(parent Context, key, val interface{
}) Context
当多个协程运行的时候,如果有一个协成出现问题,我们希望关闭所有的协程的时候,使用Contetxt
//计算函数 - 开启gogroutine
func Calculate(ctx context.Context, url string) error {
result := make(chan int)
err := make(chan error)
go func() {
// 进行RPC调用,并且返回是否成功,成功通过result传递成功信息,错误通过error传递错误信息
if true {
result <- 1
} else {
err <- errors.New("some error happen")
}
}()
select {
case <-ctx.Done(): // 其他调用调用失败
return ctx.Err()
case e := <-err: // 本协同调用失败,返回错误信息
return e
case <-result: // 本携程调用成功,不返回错误信息
return nil
}
}
//主函数
func main() {
ctx, cancel := context.WithCancel(context.Background())
err := Rpc(ctx, "http://rpc_1_url")
if err != nil {
return
}
wg := sync.WaitGroup{
}
wg.Add(1)
go func() {
defer wg.Done()
err := Rpc(ctx, "string1")
if err != nil {
cancel()
}
}()
//其他协成调用,同上
wg.Wait()
}
官方例子,设置超时时间为50毫秒,在1秒的时候会有输出,但是50毫秒时间到了就超时了,直接报错。
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
http请求超时的使用,http实现Context接口,直接设置超时时间。
uri := "https://httpbin.org/delay/3"
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
log.Fatalf("http.NewRequest() failed with '%s'\n", err)
}
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100)
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("http.DefaultClient.Do() failed with:\n'%s'\n", err)
}
defer resp.Body.Close()
Context可以携带key-value对应的结构值,所以我们可以通过request传输携带值的Context,而在对应的服务器进行解析
type FooKey string
var UserName = FooKey("user-name")
var UserId = FooKey("user-id")
func foo(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), UserId, "1")
ctx2 := context.WithValue(ctx, UserName, "yejianfeng")
next(w, r.WithContext(ctx2))
}
}
func GetUserName(context context.Context) string {
if ret, ok := context.Value(UserName).(string); ok {
return ret
}
return ""
}
func GetUserId(context context.Context) string {
if ret, ok := context.Value(UserId).(string); ok {
return ret
}
return ""
}
func test(w http.ResponseWriter, r *http.Request) {
//获取key对应的value
w.Write([]byte(GetUserId(r.Context())))
w.Write([]byte(GetUserName(r.Context())))
}
func main() {
http.Handle("/", foo(test))
http.ListenAndServe(":8080", nil)
}