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)
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章