【Golang】context

学到到上下文了,在swoole中有说过上下文的作用,上下文主要是用来隔离变量的,当多进程/协程并发运行中,可能会出现需要用到同一变量的问题,使用上下文可以很好的做到变量间的隔离防止污染。 在golang中的上下文当然不止swoole中自己实现的那些功能了,golang中的上下文不止可以用于变量的隔离,还可以用来管理协程运行。不多说了直接阅读文档吧

package context

import "context"

Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.

  1. 上下文包内定义了变量类型 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.

  1. 在服务接受请求的时候应该创建一个上下文,在对外调用的时候应该接受一个上下文。函数的调用链间应该做到上下文传播,也可以选择用 WithCancel, WithDeadline, WithTimeout, or WithValue 来管理上下文传播。当一个上下文被取消了,所有的子上下文都应该被取消。

The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived Context (the child) and a CancelFunc. Calling the CancelFunc cancels the child and its children, removes the parent's reference to the child, and stops any associated timers. Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. The go vet tool checks that CancelFuncs are used on all control-flow paths.

  1. WithCancel, WithDeadline, and WithTimeout 这三个方法会接收一个上下文并创建返回一个子上下文和取消方法 CancelFunc,调用取消方法来取消并删除所有的子上下文并停止所有相关的定时器。调用 CancelFunc 失败会导致子上下文泄露直到父上下文被取消或者定时器到期。使用 go vet 工具检查程序中的控制流路径是否有使用 CancelFuncs

Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:

  1. 使用上下文的程序应该遵循以下规则来保证上下文传播一致:

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:

func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}
  1. 不要讲上下文存在一个结构体中,而应该显示的传递给每一个需要使用它的地方。上下文应该作为第一个参数被传入方法/函数中,通常被命名为 ctx

Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.

  1. 即使程序允许也不要传空指针上下文。如果你不确定上下文会怎么用,可以通过 context.TODO 创建上下文。

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

  1. 只有在进程间通信和API请求数据中使用上下文,而不是作为可选参数传给一个函数或方法

The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.

  1. 同一个上下文可能会运行在不同的协程中,上下文是并发安全的。

See https://blog.golang.org/context for example code for a server that uses Contexts.

  1. 查看用例可以前往:https://blog.golang.org/context

通过包阅读可以了解到上下文的部分信息:

  1. 在进程间通信或者api请求中创建上下文来保存数据
  2. 上下文是树状的派生结构,并且暴露出了 WithCancel, WithDeadline, and WithTimeout 这三个方法来管理上下文的运行
  3. 通过调用 CancelFuncs 来中取消上下文,因为上下文是派生的可传播的树状结构,所以在父节点取消关闭前,需要把子节点也都关闭,不然会出现上下文泄露的问题
  4. 因为上下文是可能泄露的,所以请明文显性使用上下文,不要将上下文藏在某个结构体内传播
  5. 在不知道会不会使用上下文的时候请使用 context.TODO 来创建一个上下文传播而不是使用空指针
  6. 上下文是并发安全的

type CancelFunc

type CancelFunc func()

A CancelFunc tells an operation to abandon its work. A CancelFunc does not wait for the work to stop. After the first call, subsequent calls to a CancelFunc do nothing.

  1. CancelFunc用来告诉程序取消操作,CancelFunc不需要等待程序停止,在CancelFunc第一次调用后,重复调用不会发生任何事情

type Context

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.
    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.
    //
    // 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.
    //
    // 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 cancelation.
    Done() <-chan struct{}

    // Err returns a non-nil error value after Done is closed. Err returns
    // Canceled if the context was canceled or DeadlineExceeded if the
    // context's deadline passed. No other values for Err are defined.
    // After Done is closed, successive calls to Err return the same value.
    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 = 0
    //
    // 	// 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{}
}

A Context carries a deadline, a cancelation signal, and other values across API boundaries.

Context's methods may be called by multiple goroutines simultaneously.

  1. 上下文跨API边界携带终止时间,取消的信号和其他的值
  2. 上下文的方法可以被并发使用,是并发安全的
Deadline() (deadline time.Time, ok bool)

Deadline() 返回上下文的截止时间,当没有设置deadline的时候调用Deadline()会返回 ok==false ,连续调用 Deadline() 返回的结果是相同的。

Done() <-chan struct{}

Done() 会返回一个只读的通道,当上下文关闭或取消的时候这个通道会被关闭,如果上下文永远不会取消,这个方法可能会返回 nil。

通道会在WithCancel手动取消上下文,WithDeadline上下文到了截止时间,WithTimeout上下文超时的时候关闭。

Done() 一般会在select语句中使用:

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:
		}
	}
}
Err() error

Err() 在通道 Done() 的通道关闭后返回一个错误:

  1. 如果上下文被取消,返回 Canceled
  2. 如果上下文到了截止日志,返回 DeadlineExceeded
Value(key interface{}) interface{}

使用 Value() 来获取存储的键值对,如果没有和键相关联的值则会返回nil。 键值存储应该仅用于API边界和进程间通信的请求域的数据,而不是用于向函数或方法中传递数据。 上下文中的一个键中应存储一个特定的值。使用上下文中的键值对的地方一般会定义一个全局变量,然后将这个全局变量作为 context.WithValueContext.Value 的参数。key可以传递任何可以做等于运算的数据类型,key的类型应该是一个非对外开放的数据类型以避免冲突。

在包内应该定义一个变量key用来安全的访问上下文:

import "context"

// User is the type of value stored in the Contexts.
// User是用来存储到上下文的数据类型
type User struct{ ... }

// key is an unexported type for keys defined in this package.
// key是一个不对外暴露的数据类型,用来定义上下文内值的键
// 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.
// userKey是上下文中的值的真正的键,它也是不对外暴露的
// 应该使用`user.NewContext`和`user.FromContext`而不是直接使用userKey
var userKey key = 0

// NewContext returns a new Context that carries value u.
// NewContext()返回一个新的上下文,里面存储了一个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.
// FromContext返回存储在上下文中的u
func FromContext(ctx context.Context) (*User, bool) {
	u, ok := ctx.Value(userKey).(*User)
	return u, ok
}

functions

func Background() Context 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.

  1. Background()返回一个非空指针的空上下文,它永远不会被取消,没有任何值,没有截止时间,它用于main函数,用于当做顶级上下文。

func TODO() 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.

  1. TODO()返回一个非空指针的空上下文,当不知道是否需要使用或者需要使用什么上下文的时候使用context.TODO来创建一个上下文用于正常的传播

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 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.

  1. WithCancel()返回一个父上下文的拷贝,这个拷贝中有一个自己的Done通道。这个上下文的Done通道会在他返回的CancelFunc被调用的时候或者父上下文的Done通道关闭的时候关闭。

  2. 取消这个通道可以释放资源,所以程序应该在上下文完成后尽快调用CancelFunc

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

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.

  1. WithDeadline()返回一个含有截止日期的父上下文的备份,如果父节点的截止日期早于子节点的截止日期,那么这两个上下文可以视为是一样的(因为父节点的截止时间到了后就会关闭,关闭父节点后,所有子节点也会关闭)。返回的上下文的Done通道将在截止日期过期后也会关闭。

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).

  1. WithTimeout() 在实质上和WithDeadline()是一样的,不同的是WithDeadline()需要传入一个time.Time,而WithTimeout()是通过time.Now().Add(timeout)计算出截止时间。

func WithValue(parent Context, key, val interface{}) 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.

  1. WithValue()返回一个带有kv的父上下文的拷贝。
  2. 注入的键必须是一个可比较的数据类型并且不应该设置为字符串或其他内置的数据类型以防冲突。使用WithValue的时候应该定义自己的key类型。

实际操作一下试试

WithCancel

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
)

var topCtx = context.Background()
var wg = sync.WaitGroup{}

func doJob(ctx context.Context, t int) error {
	errCh := make(chan error)
	fmt.Println("t =", t)
	if t == 2 { // 模拟一个错误的操作
		go func() {
			time.Sleep(time.Second * 2)
			errCh <- errors.New("error")
		}()
	} else {
		go func() {
			time.Sleep(time.Second * time.Duration(t))
			errCh <- nil
			fmt.Println("success", t)
		}()
	}
	select {
	case err := <-errCh:
		return err
	case <-ctx.Done():
		fmt.Println("fail", t)
		return ctx.Err()
	}
}

func main() {
	ctx, cancel := context.WithCancel(topCtx)
	defer cancel()
	// 创建顶级协程
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			err := doJob(ctx, i)
			if err != nil {
				fmt.Println(err)
				cancel()
			}
		}(i)
	}
	wg.Wait()
}
t = 4
t = 1
t = 2
t = 3
t = 0
success 0
success 1
error
fail 4
context canceled
fail 3
context canceled
roll back

这里模拟一组操作,如果全部成功就往下执行,如果有任意一个失败就跳过等待。

WithTimeout

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var topCtx = context.Background()
var wg = sync.WaitGroup{}

func doJob(ctx context.Context, t int) error {
	errCh := make(chan error)
	fmt.Println("t =", t)
	go func() {
		time.Sleep(time.Second * time.Duration(t))
		errCh <- nil
		fmt.Println("success", t)
	}()
	select {
	case err := <-errCh:
		return err
	case <-ctx.Done():
		fmt.Println("fail", t)
		return ctx.Err()
	}
}

func main() {
	ctx, cancel := context.WithTimeout(topCtx, time.Second*2)
	defer cancel()
	// 创建顶级协程
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			err := doJob(ctx, i)
			if err != nil {
				fmt.Println(err)
				cancel()
			}
		}(i)
	}
	wg.Wait()
}

模拟一组接口请求,超过2秒视为超时直接退出(错误),一个请求错误(退出),所有请求跳过。

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论