前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang context实战

golang context实战

原创
作者头像
王磊-字节跳动
发布2019-10-21 11:08:53
1.7K0
发布2019-10-21 11:08:53
举报
文章被收录于专栏:01ZOO01ZOO

context 实际上并不是一个罕见的设计,在许多语言的基础库里面都有出现,有所不同的是,大部分 sdk,尤其是过程语言,使用context 一般是用来传递数据,golang 的 context设计同时还带有传递控制能力的作用,实现方式为使用 cancel/timeout.

概述

来自官方文档: https://blog.golang.org/context: Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.

上层请求创建 context,附带信息或者控制能力,传递给下一层,当上一层出现错误,或者取消的时候,下次收到信号,也取消处理,避免浪费。

代码语言:txt
复制
main goroutine --context1--> goroutine1 --context2--> goroutine2

接口

Context 是 context 包对外暴露的接口,该接口定义了四个方法,其中包括:

  • Deadline 方法需要返回当前 Context 被取消的时间,也就是完成工作的截止日期;
  • Done 方法需要返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
  • Err 方法会返回当前 Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;
    • 如果当前 Context 被取消就会返回 Canceled 错误;
    • 如果当前 Context 超时就会返回 DeadlineExceeded 错误;
  • Value 方法会从 Context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,这个功能可以用来传递请求特定的数据;
代码语言:txt
复制
// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}

示例

例子1

官方例子

代码语言:txt
复制
// 一个关联知识点 
// 1. 对 nil channel中读写数据会一直被block。
// 2. close的channel 读立即返回零值,写会panic,无论读写都不会阻塞。

// 常用的使用模式 break if cancel 
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:
  		}
  	}
}

// 简化的 google search 例子
func handleSearch(w http.ResponseWriter, req *http.Request) {
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
	if err == nil {
		ctx, cancel = context.WithTimeout(context.Background(), timeout)
	} else {
		ctx, cancel = context.WithCancel(context.Background())
	}
	defer cancel() // Cancel ctx as soon as handleSearch returns.
	
	results, err := search(ctx, query)
	// ...略
}

// Search sends query to Google search and returns the results.
func Search(ctx context.Context, query string) (Results, error) {
	// ...略
	err = httpDo(ctx, req, func(resp *http.Response, err error) error {
		// 略
		results = xxx
		return nil
	})
	// httpDo waits for the closure we provided to return, so it's safe to read results here.
	return results, err
}

// httpDo issues the HTTP request and calls f with the response. If ctx.Done is
// closed while the request or f is running, httpDo cancels the request, waits
// for f to exit, and returns ctx.Err. Otherwise, httpDo returns f's error.
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
	// Run the HTTP request in a goroutine and pass the response to f.
	c := make(chan error, 1)
	req = req.WithContext(ctx)
	go func() { c <- f(http.DefaultClient.Do(req)) }()
	select {
	case <-ctx.Done():// 被取消了 (可能是 timeout 触发的)
		<-c // Wait for f to return.
		return ctx.Err()
	case err := <-c:
		return err
	}
}

例子2:

来自 rancher/wranger

代码语言:txt
复制
// 另一个例子 这是一个收到一个信息 stop 两个信号退出的函数
func SetupSignalHandler(parent context.Context) context.Context {
	close(onlyOneSignalHandler) // panics when called twice
	ctx, cancel := context.WithCancel(parent)

	c := make(chan os.Signal, 2)
	signal.Notify(c, shutdownSignals...)
	go func() {
		<-c
		cancel() // 收到信号,取消 ctx, 后面使用这个 ctx 的任务都会 done
		<-c
		os.Exit(1) // second signal. Exit directly.
	}()

	return ctx
}

例子3

来自 kubernetes scheduler

代码语言:txt
复制
// 另一个例子 来自kubernetes scheduler
func Run(c schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}) error {
	// ...略
	// Prepare a reusable run function.
	run := func(ctx context.Context) {
		sched.Run()
		<-ctx.Done()
	}

	ctx, cancel := context.WithCancel(context.TODO()) // TODO once Run() accepts a context, it should be used here
	defer cancel()

	go func() {
		select {
		case <-stopCh:
			cancel()
		case <-ctx.Done():
		}
	}()

	// If leader election is enabled, run via LeaderElector until done and exit.
	if c.LeaderElection != nil {
		// ...略
		leaderElector.Run(ctx)
		return fmt.Errorf("lost lease")
	}

	// Leader election is disabled, so run inline until done.
	run(ctx)
	return fmt.Errorf("finished without leader elect")
}

例子4

实战:再来最后一个例子,如何实现这样一个函数, retry f,直到 f 成功或者 timeout

对于这个例子 更通用的实现见 k8s.io/apimachinery/pkg/util/wait/wait.go

不过 wait 中的 timeout 并不准确, 它在 重复的时候才会检查 timeout

代码语言:txt
复制
func Retry(fn func(ctx context.Context) error, timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	c := make(chan error, 1)
	for {
		go func() { c <- fn(ctx) }()
		select {
		case <-ctx.Done():
			return ctx.Err() // timeout error
		case err := <-c:
			if err == nil {
				return nil
			}
		}
	}
}

参考:

https://draveness.me/golang/concurrency/golang-context.html

https://golang.org/pkg/context/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 接口
  • 示例
    • 例子1
      • 例子2:
        • 例子3
          • 例子4
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档