前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >golang | context dive

golang | context dive

作者头像
heidsoft
发布2022-06-09 18:09:46
发布2022-06-09 18:09:46
39600
代码可运行
举报
运行总次数:0
代码可运行
代码语言:javascript
代码运行次数:0
运行
复制
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))
}

Introduction:

Definition:

Context is a package provided by GO. Let’s first understand some problems that existed already, and which context package tries to solve.

上下文是 GO 提供的包。让我们首先了解一些已经存在的问题,以及哪个上下文包试图解决。

Problem Statement:

  • Let’s say that you started a function and you need to pass some common parameters to the downstream functions. You cannot pass these common parameters each as an argument to all the downstream functions.
  • 假设您启动了一个函数,并且需要将一些常用参数传递给下游函数。不能将这些公共参数作为参数传递给所有下游函数。
  • You started a goroutine which in turn start more goroutines and so on. Suppose the task that you were doing is no longer needed. Then how to inform all child goroutines to gracefully exit so that resources can be freed up
  • 你开始了一个goroutine,反过来又开始了更多的goroutines等等。假设不再需要您正在执行的任务。然后如何通知所有孩子优雅地退出,以便释放资源
  • A task should be finished within a specified timeout of say 2 seconds. If not it should gracefully exit or return.
  • 任务应在指定的超时(例如 2 秒)内完成。如果不是,它应该优雅地退出或返回。
  • A task should be finished within a deadline eg it should end before 5 pm . If not finished then it should gracefully exit and return
  • 任务应在截止日期内完成,例如它应该在下午5点之前结束。如果未完成,则应正常退出并返回

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)需要可供所有下游函数使用。

When to Use (Some Use Cases):

  • To pass data to the downstream. Eg. a HTTP request creates a request_id, request_user which needs to be passed around to all downstream functions for distributed tracing.
  • 将数据传递到下游。例如。HTTP 请求创建一个request_id,request_user需要将其传递给所有下游函数以进行分布式跟踪。
  • When you want to halt the operation in the midway – A HTTP request should be stopped because the client disconnected
  • 当您想要中途停止操作时 – 应停止 HTTP 请求,因为客户端已断开连接
  • When you want to halt the operation within a specified time from start i.e with timeout – Eg- a HTTP request should be completed in 2 sec or else should be aborted.
  • 当您想在开始的指定时间内停止操作时,即超时 - 例如 - HTTP请求应在2秒内完成,否则应中止。
  • When you want to halt an operation before a certain time – Eg. A cron is running that needs to be aborted in 5 mins if not completed.
  • 当您想在特定时间之前停止操作时 – 例如。正在运行一个 cron,如果未完成,则需要在 5 分钟内中止。

Context Interface

The core of the understanding context is knowing the Context interface

理解上下文的核心是了解上下文接口

代码语言:javascript
代码运行次数:0
运行
复制
// 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{}
}

Creating New Context

context.Background():

context package function Background() returns a empty Context which implements the Context interface

上下文包 Background() 返回一个空的 Context,该上下文实现 Context 接口

  1. It has no values 它没有值
  2. It is never canceled 它永不取消
  3. It has no deadline 它没有截止日期

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()有什么用,它将作为从中派生的所有上下文的根。随着我们的前进,它将更加清晰。

context.ToDo():

  • context package ToDo function returns an empty Context. This context is used when the surrounding function has not been passed a context and one wants to use the context as a placeholder in the current function and plans to add actual context in the near future. One use of adding it as a placeholder is that it helps in validation in the Static Code Analysis tool.
  • 上下文包 ToDo 函数返回一个空的上下文。当周围的函数尚未传递上下文,并且希望将上下文用作当前函数中的占位符并计划在不久的将来添加实际上下文时,将使用此上下文。将其添加为占位符的一个用途是,它有助于在静态代码分析工具中进行验证。
  • It is also an empty Context same as context.Background()
  • 它与context.Background() 一样,返回一个空的context

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

上述两种方法描述了一种创建新上下文的方法。可以从这些上下文中派生出更多上下文。这就是上下文树进入画面的地方

Context Tree

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

代码语言:javascript
代码运行次数:0
运行
复制
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

  • rootCtx is the empty Context with no functionality
  • childCtx is derived from rootCtx and has the functionality of storing request-scoped values. In above example it is storing key-value pair of {"msgId" : "someMsgId"}
  • childCtx派生自rootCtx,具有存储请求范围值的功能。在上面的示例中,它存储了 {"msgId" : "someMsgId"}

Three level tree

代码语言:javascript
代码运行次数:0
运行
复制
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

  • rootCtx is the empty Context with no functionality. 空Context 没有任何功能
  • childCtx is derived from rootCtx and has the functionality of storing request-scoped values. In above example it is storing key-value pair of {"msgId" : "someMsgId"}
  • childOfChildCtx is derived from childCtx . It has the functionality of storing request-scoped values and also it has the functionality of triggering cancellation signals. cancelFunc can be used to trigger cancellation signals
  • childOfChildCtx 派生自 childCtx 。它具有存储请求范围值的功能,并且还具有触发取消信号的功能取消侦听可用于触发取消信号

Multi-level tree

代码语言:javascript
代码运行次数:0
运行
复制
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"))
}

Deriving From Context

A derived(派生) context is can be created in 4 ways

  • Passing request-scoped values - using WithValue() function of context package
  • With cancellation signals - using WithCancel() function of context package
  • With deadlines - using WithDeadine() function of context package
  • With timeouts - using WithTimeout() function of context package

Let's understand each of the above in details

context.WithValue()

Used for passing request-scoped values. The complete signature of the function is

代码语言:javascript
代码运行次数:0
运行
复制
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.

代码语言:javascript
代码运行次数:0
运行
复制
#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请求链路注入场景

代码语言:javascript
代码运行次数:0
运行
复制
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 执行任务主动退出场景

代码语言:javascript
代码运行次数:0
运行
复制
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 执行任务超时退出场景

代码语言:javascript
代码运行次数:0
运行
复制
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))
}
代码语言:javascript
代码运行次数:0
运行
复制
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 执行任务到期退出场景

代码语言:javascript
代码运行次数:0
运行
复制
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/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云数智圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Introduction:
    • Definition:
    • Context is a package provided by GO. Let’s first understand some problems that existed already, and which context package tries to solve.
    • Problem Statement:
    • When to Use (Some Use Cases):
  • Context Interface
  • Creating New Context
    • context.Background():
    • context.ToDo():
  • Context Tree
  • Deriving From Context
    • context.WithValue()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档