49

golang的context

 5 years ago
source link: https://studygolang.com/articles/18841?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

先罗列一些思路,之后再仔细整理。

  1. 1.7中context在很多官方包中才开始普及(1.5才有?)

2. context在一般开发中的作用,和一些用法的约定

3. 官方context的实现,以及cancelContext和timerContext的实现

4. context在nuwa-golang中的应用

1.context的前世今生

context顾名思义是上下文,用来传递一次处理过程中的信息。传统意义上,上下文直接通过大数组(php)之类的结构传递即可,但是在golang中不一样,因为goroutine的存在,导致并发下的竞态条件很容易触发。为了方便在goroutine中传递信息,同事管理和约束一次请求的所有goroutines,golang官方在go1.7中把 golang.org/x/net/context 纳入了标准库,并在net/http等标准库中广泛使用了context包。

相关资料

golang官方文档: https://blog.golang.org/context

pipelines(close(chan)广播退出)的官方文档: https://blog.golang.org/context

参考文章: https://www.cnblogs.com/tianlongtc/articles/8824740.html https://blog.csdn.net/zdyueguanyun/article/details/64904703 https://studygolang.com/articles/10155?fr=sidebar

2.context是使用场景

主要用户多个goroutine之间的管理。比如,请求终止时退出所有相关的的goroutine,定时退出所有的goroutine,goroutine之间传递数据。

注意: 使用时遵循context规则
1\. 不要将 Context放入结构体,Context应该作为第一个参数传入,命名为ctx。
2\. 即使函数允许,也不要传入nil的 Context。如果不知道用哪种Context,可以使用context.TODO()。
3\. 使用context的Value相关方法,只应该用于在程序和接口中传递和请求相关数据,不能用它来传递一些可选的参数
4\. 相同的 Context 可以传递给在不同的goroutine;Context 是并发安全的。

3.context的源码分析

context基类如下所示,golang提供了两种初始化的方法,四中生成子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). TODO is recognized by static analysis tools that determine
// whether Contexts are propagated correctly in a program.
context.TODO()

// 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.
context.Background()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context

具体的数据结构如下所示,其中cancelCtx实现原理主要是依靠close(chan)发送广播,timerCtx基本同cancelCtx类似,valueCtx通过构造参数树,来保证在并发情况下,在这个并发树上的参数不会出现一致性问题。

// Context's methods may be called by multiple goroutines simultaneously.
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.
   // 返回一个time.Time,是当前 Context 的应该结束的时间,ok 表示是否有 deadline
   Deadline() (deadline time.Time, ok bool)

   // a Done channel for cancelation.
   // 返回一个struct{}类型的只读 channel
   Done() <-chan struct{}

   // After Err returns a non-nil error, successive calls to Err return the same error.
   // 返回 Context 被取消时的错误
   Err() error

   // 是 Context 自带的 K-V 存储功能
   Value(key interface{}) interface{}
}

type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

// emptyCtx是空的Context,只实现了Context interface,只能作为 root context 使用。
type emptyCtx int

// cancelCtx继承了Context并实现了cancelerinterface,从WithCancel()函数产生
type cancelCtx struct {
    Context
    done chan struct{} // closed by the first cancel call.
    mu       sync.Mutex
    children map[canceler]bool // set to nil by the first cancel call
    err      error             // set to non-nil by the first cancel call
}

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
// timerCtx继承了cancelCtx,所以也自然实现了Context和canceler这两个interface,由WithDeadline()函数产生
type timerCtx struct {
   cancelCtx
   timer *time.Timer // Under cancelCtx.mu.

   deadline time.Time
}

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
// valueCtx包含key、val field,可以储存一对键值对,由WithValue()函数产生
type valueCtx struct {
   Context
   key, val interface{}
}

4.context在官方包中的示例

官方给出了再常见情况下的几种示例,其中包括了cancel,deadline,value三种场景下的简单实用方法。

//context/example_test.go

// This example demonstrates the use of a cancelable context to prevent a
// goroutine leak. By the end of the example function, the goroutine started
// by gen will return without leaking.
func ExampleWithCancel() {
   // gen generates integers in a separate goroutine and
   // sends them to the returned channel.
   // The callers of gen need to cancel the context once
   // they are done consuming generated integers not to leak
   // the internal goroutine started by gen.
   gen := func(ctx context.Context) <-chan int {
      dst := make(chan int)
      n := 1
      go func() {
         for {
            select {
            case <-ctx.Done():
               return // returning not to leak the goroutine
            case dst <- n:
               n++
            }
         }
      }()
      return dst
   }

   ctx, cancel := context.WithCancel(context.Background())
   defer cancel() // cancel when we are finished consuming integers

   for n := range gen(ctx) {
      fmt.Println(n)
      if n == 5 {
         break
      }
   }
   // Output:
   // 1
   // 2
   // 3
   // 4
   // 5
}

// This example passes a context with an arbitrary deadline to tell a blocking
// function that it should abandon its work as soon as it gets to it.
func ExampleWithDeadline() {
   d := time.Now().Add(50 * time.Millisecond)
   ctx, cancel := context.WithDeadline(context.Background(), d)

   // Even though ctx will be expired, it is good practice to call its
   // cancelation function in any case. Failure to do so may keep the
   // context and its parent alive longer than necessary.
   defer cancel()

   select {
   case <-time.After(1 * time.Second):
      fmt.Println("overslept")
   case <-ctx.Done():
      fmt.Println(ctx.Err())
   }

   // Output:
   // context deadline exceeded
}

// This example passes a context with a timeout to tell a blocking function that
// it should abandon its work after the timeout elapses.
func ExampleWithTimeout() {
   // Pass a context with a timeout to tell a blocking function that it
   // should abandon its work after the timeout elapses.
   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"
   }

   // Output:
   // context deadline exceeded
}

func ExampleWithValue() {
   type favContextKey string

   f := func(ctx context.Context, k favContextKey) {
      if v := ctx.Value(k); v != nil {
         fmt.Println("found value:", v)
         return
      }
      fmt.Println("key not found:", k)
   }

   k := favContextKey("language")
   ctx := context.WithValue(context.Background(), k, "Go")

   f(ctx, k)
   f(ctx, favContextKey("color"))

   // Output:
   // found value: Go
   // key not found: color
}

context在http包中的应用,

// net/http中ctx的创建
func (srv *Server) Serve(l net.Listener) error {
   defer l.Close()
   if fn := testHookServerServe; fn != nil {
      fn(srv, l)
   }
   var tempDelay time.Duration // how long to sleep on accept failure

   if err := srv.setupHTTP2_Serve(); err != nil {
      return err
   }

   srv.trackListener(l, true)
   defer srv.trackListener(l, false)

   baseCtx := context.Background() // base is always background, per Issue 16220
   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
   for {
      rw, e := l.Accept()
      if e != nil {
         select {
         case <-srv.getDoneChan():
            return ErrServerClosed
         default:
         }
         if ne, ok := e.(net.Error); ok && ne.Temporary() {
            if tempDelay == 0 {
               tempDelay = 5 * time.Millisecond
            } else {
               tempDelay *= 2
            }
            if max := 1 * time.Second; tempDelay > max {
               tempDelay = max
            }
            srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
            time.Sleep(tempDelay)
            continue
         }
         return e
      }
      tempDelay = 0
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew) // before Serve can return
      go c.serve(ctx)
   }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK