context.Context

In golang control concurrency (sync.WaitGroup and context.Context) , we have talked about the role of context and its simple use. Now let's expand it to talk about the other things about context

Context interface

The interface definition of Context is relatively concise. Let's look at the methods of this interface.

type Context interface {
    
    
	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{
    
    }

	Err() error

	Value(key interface{
    
    }) interface{
    
    }
}

There are 4 methods in this interface. It is very important to understand the meaning of these methods so that we can use them better.

The Deadline method is to get the set deadline. The first return type is the deadline. At this point in time, Context will automatically initiate a cancellation request; the second return value ok==false means that the deadline is not set, if needed To cancel, you need to call the cancel function to cancel.

The Done method returns a read-only chan whose type is struct{}. We are in goroutine. If the chan returned by this method can be read, it means that the parent context has initiated a cancellation request. After we receive this signal through the Done method , You should do a cleanup operation, and then exit the goroutine to release resources.

The Err method returns the error reason for the cancellation, because of what Context was cancelled.

The Value method obtains the value bound to the Context, which is a key-value pair, so the corresponding value can be obtained through a Key. This value is generally thread-safe.

The most commonly used of the above four methods is Done. If the Context is canceled, we can get a closed chan. The closed chan can be read, so as long as it can be read, it means that the Context is canceled. The following is the classic usage of this method.

  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:
  		}
  	}
  }

The Context interface does not need to be implemented by us. Go's built-in has helped us implement two. At the beginning of our code, we used these two built-in as the top-level partent context to derive more child contexts.

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

func Background() Context {
    
    
	return background
}

func TODO() Context {
    
    
	return todo
}

One is Background, mainly used in the main function, initialization and test code, as the top Context of the Context tree structure, which is the root Context.

One is TODOthat it currently does not know the specific usage scenario, if we do not know what Context should be used, we can use this.

Both of them are essentially emptyCtx structure types, which are non-cancellable Contexts with no deadline set and no value.

type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    
    
	return
}

func (*emptyCtx) Done() <-chan struct{
    
    } {
    
    
	return nil
}

func (*emptyCtx) Err() error {
    
    
	return nil
}

func (*emptyCtx) Value(key interface{
    
    }) interface{
    
    } {
    
    
	return nil
}

This is how emptyCtx implements the Context interface. As you can see, these methods do nothing and return nil or zero.

Inheritance and derivation of Context
With the above root Context, how to derive more child Context? This depends on the With series of functions provided by the context package.

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, val interface{
    
    }) Context
这四个 With 函数,接收的都有一个 partent 参数,就是父 Context,我们要基于这个父 Context 创建出子 Context 的意思,这种方式可以理解为子 Context 对父 Context 的继承,也可以理解为基于父 Context 的衍生。

通过这些函数,就创建了一颗 Context 树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。

WithCancel function , pass a parent Context as a parameter, return the child Context, and a cancel function to cancel the Context. The WithDeadline function is similar to WithCancel. It will pass an additional deadline parameter, which means that the Context will be automatically canceled at this point in time. Of course, we can also cancel it in advance through the cancel function without waiting for this time.

WithTimeout and WithDeadline are basically the same. This means that the context is automatically cancelled after a timeout.

WithValue function has nothing to do with canceling Context. It is to generate a Context bound with a key-value pair data. The bound data can be accessed through the Context.Value method, which we will talk about later.

You may have noticed that the first three functions all return a cancellation function CancelFunc , which is a function type and its definition is very simple.

type CancelFunc func()

This is the type of cancel function, which can cancel a Context and all Contexts under this node Context, no matter how many levels there are.

WithValue Passing Metadata
Through the Context, we can also pass some necessary metadata, which will be attached to the Context for use.

var key string = "name"

func main() {
    
    
	ctx, cancel := context.WithCancel(context.Background())
	//附加值
	valueCtx := context.WithValue(ctx, key, "【监控1】")
	go watch(valueCtx)
	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
    
    
	for {
    
    
		select {
    
    
		case <-ctx.Done():
		//取出值
			fmt.Println(ctx.Value(key), "监控退出,停止了...")
			return
		default:
		//取出值
			fmt.Println(ctx.Value(key), "goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

In the previous example, we passed the value of name to the monitoring function by passing parameters. In this example, we achieve the same effect, but through the value of Context.

We can use the context.WithValue method to attach a pair of KV key-value pairs, where the Key must be equivalent, that is, comparable; Value must be thread-safe.

In this way, we have generated a new Context, this new Context has this key-value pair, when in use, you can read ctx.Value(key) through the Value method.

Remember, using WithValue to pass values ​​is generally a necessary value, don't pass all values. ( 原因待查)

Context usage principles

Don't put Context in the structure, but pass it
as a parameter. For function methods that take Context as a parameter, you should put Context as the first parameter and put it first.
When passing Context to a function method, do not pass nil. If you don’t know what to pass, use context.TODO
Context’s Value related methods should pass the necessary data. Don’t use this to pass all data.
Context is thread-safe and you can rest assured To pass in multiple goroutines

References: In-
depth understanding of Golang's context
-oriented programming and talk about context

Guess you like

Origin blog.csdn.net/csdniter/article/details/109732023