Go:官方库 -Context

参考:Golang中 Context包深入浅出
参考:golang中Context的使用场景

1、作用说明

一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。
所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,它就是goroutine的上下文。
也就是说面对多个gogroutine可能又开启子gogroutine的情况,我们可以通过context包来控制这些协程的声明周期

2、方法说明

1、Context接口

1、Dealine

获取设置的截止时间,deadline是截止时间,到了这个时间点,Context会自动发起取消请求;
返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。

	// 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)

2、Done

返回一个只读的chan,类型为struct{}。
我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,否则返回nil

	// 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.
	// Done is provided for use in select statements:
	Done() <-chan struct{}

3、Err

如果还没有被关闭,返回nil。否则返回取消的错误原因。

	// 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

4、Value

返回Context上绑定的数据,通过key来获取对应的value

	Value(key interface{}) interface{}

2、Background

主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Contex

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

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

4、WithCancel

返回一个子Context和一个CanceFunc函数,当调用CancelFunc或者父Context结束的时候,这个Context也会关闭

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

5、WithDeadline

返回一个子Context。传入的时间d为最后截至时间,然后父Context如果结束的话,还没到截止时间,子Context也会结束。

	// 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.
	func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 

6、WithTimeout

底层也是调用WithDeadline,在多少时间之后超时关闭。

	// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
	//
	// Canceling this context releases resources associated with it, so code should
	// call cancel as soon as the operations running in this Context complete:
	//
	// 	func slowOperationWithTimeout(ctx context.Context) (Result, error) {
	// 		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
	// 		defer cancel()  // releases resources if slowOperation completes before timeout elapses
	// 		return slowOperation(ctx)
	// 	}
	func WithTimeout(parent Context, timeout time.Duration)

7、WithValue

生成一个带key-value值得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.
	func WithValue(parent Context, key, val interface{}) Context

3、使用示例

1、控制一组gogroutine

当多个协程运行的时候,如果有一个协成出现问题,我们希望关闭所有的协程的时候,使用Contetxt

	//计算函数 - 开启gogroutine
	func Calculate(ctx context.Context, url string) error {
		result := make(chan int)
		err := make(chan error)
	
		go func() {
			// 进行RPC调用,并且返回是否成功,成功通过result传递成功信息,错误通过error传递错误信息
			if true {
				result <- 1
			} else {
				err <- errors.New("some error happen")
			}
		}()
	
		select {
		case <-ctx.Done(): // 其他调用调用失败
			return ctx.Err()
		case e := <-err: // 本协同调用失败,返回错误信息
			return e
		case <-result: // 本携程调用成功,不返回错误信息
			return nil
		}
	}
	//主函数
	func main() {
		ctx, cancel := context.WithCancel(context.Background())
	
		err := Rpc(ctx, "http://rpc_1_url")
		if err != nil {
			return
		}
		wg := sync.WaitGroup{}
	
		wg.Add(1)
		go func() {
			defer wg.Done()
			err := Rpc(ctx, "string1")
			if err != nil {
				cancel()
			}
		}()
	
		//其他协成调用,同上
		wg.Wait()
	}

2、超时请求

官方例子,设置超时时间为50毫秒,在1秒的时候会有输出,但是50毫秒时间到了就超时了,直接报错。

	func main() {
	    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"
	    }
	}

http请求超时的使用,http实现Context接口,直接设置超时时间。

	uri := "https://httpbin.org/delay/3"
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
	    log.Fatalf("http.NewRequest() failed with '%s'\n", err)
	}
	
	ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100)
	req = req.WithContext(ctx)
	
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
	    log.Fatalf("http.DefaultClient.Do() failed with:\n'%s'\n", err)
	}
	defer resp.Body.Close()

3、HTTP服务器的request互相传递数据

Context可以携带key-value对应的结构值,所以我们可以通过request传输携带值的Context,而在对应的服务器进行解析

	type FooKey string
	
	var UserName = FooKey("user-name")
	var UserId = FooKey("user-id")
	
	func foo(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			ctx := context.WithValue(r.Context(), UserId, "1")
			ctx2 := context.WithValue(ctx, UserName, "yejianfeng")
			next(w, r.WithContext(ctx2))
		}
	}
	
	func GetUserName(context context.Context) string {
		if ret, ok := context.Value(UserName).(string); ok {
			return ret
		}
		return ""
	}
	
	func GetUserId(context context.Context) string {
		if ret, ok := context.Value(UserId).(string); ok {
			return ret
		}
		return ""
	}
	
	func test(w http.ResponseWriter, r *http.Request) {
		//获取key对应的value
		w.Write([]byte(GetUserId(r.Context())))
		w.Write([]byte(GetUserName(r.Context())))
	}
	
	func main() {
		http.Handle("/", foo(test))
		http.ListenAndServe(":8080", nil)
	}
发布了117 篇原创文章 · 获赞 15 · 访问量 5619

猜你喜欢

转载自blog.csdn.net/qq_34326321/article/details/104813975