<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] ## Context 對服務器傳入的請求應該創建上下文,而對服務器的傳出調用應該接受上下文。它們之間的函數調用鏈必須傳遞上下文,或者可以使用`WithCancel`、`WithDeadline`、`WithTimeout`或`WithValue`創建的派生上下文。當一個上下文被取消時,它派生的所有上下文也被取消。 ### 閱讀延申 https://mp.weixin.qq.com/s/GldXbE9z-2FkWkEs_eE1pg ### Context接口構成 ~~~ type Context interface { // 返回 context 是否設置了超時時間以及超時的時間點 // 如果沒有設置超時,那么 ok 的值返回 false // 每次調用都會返回相同的結果 Deadline() (deadline time.Time, ok bool) // 如果 context 被取消,這里會返回一個被關閉的 channel // 如果是一個不會被取消的 context,那么這里會返回 nil // 每次調用都會返回相同的結果 Done() <-chan struct{} // 返回 done() 的原因 // 如果 Done() 對應的通道還沒有關閉,這里返回 nil // 如果通道關閉了,這里會返回一個非 nil 的值: // - 若果是被取消掉的,那么這里返回 Canceled 錯誤 // - 如果是超時了,那么這里返回 DeadlineExceeded 錯誤 // 一旦被賦予了一個非 nil 的值之后,每次調用都會返回相同的結果 Err() error // 獲取 context 中保存的 key 對應的 value,如果不存在則返回 nil // 每次調用都會返回相同的結果 Value(key interface{}) interface{} } ~~~ * `Deadline`方法需要返回當前`Context`被取消的時間,也就是完成工作的截止時間(deadline); * `Done`方法需要返回一個`Channel`,這個Channel會在當前工作完成或者上下文被取消之后關閉,多次調用`Done`方法會返回同一個Channel; * `Err`方法會返回當前`Context`結束的原因,它只會在`Done`返回的Channel被關閉時才會返回非空的值; * 如果當前`Context`被取消就會返回`Canceled`錯誤; * 如果當前`Context`超時就會返回`DeadlineExceeded`錯誤; * `Value`方法會從`Context`中返回鍵對應的值,對于同一個上下文來說,多次調用`Value`并傳入相同的`Key`會返回相同的結果,該方法僅用于傳遞跨API和進程間跟請求域的數據; **其主要的應用 :** 1:上下文控制, 2:多個 goroutine 之間的數據交互等, 3:超時控制:到某個時間點超時,過多久超時。 ### Context 類型的結構體 #### emptyCtx emptyCtx 不是一個結構體,它只是 int 類型的一個別名,實現的 Context 的四個方法都是返回 nil 或者默認值: ~~~ 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 } ~~~ 這意味著 emptyCtx 永遠不能被取消,沒有 deadline,并且也不會保存任何值。它是一個私有類型,沒有提供相關的導出方法,但是卻被包裝成了兩個可以被導出的 ctx,用作頂層 Context: ~~~ var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo } ~~~ ### Background()和TODO() * background 通常可以用于 main 函數、初始化和測試,作為請求上下文的最頂層(根節點)。 * todo 當你不知道需要傳入什么樣的 context 的時候,就可以使用它,它可以隨時被替換成其他類型的 context。 實際上這倆完全沒有任何區別,但是通過不同的命名 `background`和`todo`本質上都是`emptyCtx`結構體類型,是一個不可取消,沒有設置截止時間,沒有攜帶任何值的Context。 ~~~ 創建父節點 context.Background() context.TODO() ~~~ ### With函數 #### WithCancel ~~~go func WithCancel(parent Context) (ctx Context, cancel CancelFunc) ~~~ 返回:父節點的副本,cancle的函數 #### WithDeadline ~~~go func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) ~~~ 入參:deadline 的截至時間點,代表到了這個時間就會自動取消。 eg: ~~~ ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Minute)) fmt.Println(ctx.Deadline()) //return:2022-07-18 11:39:07.3568866 +0800 CST m=+60.002051901 true ~~~ #### WithTimeout ~~~go func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) ~~~ 入參:持續時間,代表 ctx 會在多長時間之后自動取消 eg:入參是5s,那么就5秒結束 #### WithValue ~~~go func WithValue(parent Context, key, val interface{}) Context ~~~ 入參:父節點副本,key,val,設置值使用 eg: ~~~ type MySting string //定義類型 func main() { var s MySting var s1 MySting s = "log" s1 = "log1" ctx := context.WithValue(context.Background(), s, 99) fmt.Println(ctx.Value(s)) ctx1 := context.WithValue(ctx, s1, 100) fmt.Println(ctx1 .Value(s1)) } // return : 99 100 ~~~ > 一個 ctx 只能保存一對 kv,那么如果我們想要在 ctx 中保存多個 kv 鍵值對該怎么辦? 只需要多次調用?`WithValue()` 函數,每次塞進去一對 kv 即可,在取值的時候,`Value()` 方法會自動地從整個 ctx 的樹形結構中遞歸地往上層查找。所以,我們可以通過子 ctx 找到 父 ctx 維護的 kv,但是反過來是不可以的,這一點在使用的過程中需要注意。 舉個例子,如下圖所示,假設當前在 Context3,我們想要查 key1 的值,發現當前 ctx 維護的 key 不是 key1,那么會從它的父節點也就是 Context2 去找,還沒找到,便繼續往上層去找,發現 Context1 維護的 key 就是我們想要的,那么?`Value` 方法便會返回它對應的 val1。 ![](https://img.kancloud.cn/c3/96/c3969901bce79c032e0576d5a48b839f_1080x191.png) ## context 使用中的注意事項 1、context 攜帶的 kv 是向上查找的,如果當前節點查不到對應的 key,那么會繼續從其父節點中查找; 2、context 的取消操作是向下蔓延的,如果當前節點取消,那么它的子節點(cancelCtx)也會被取消; 3、使用帶有超時的 timerCtx,如果能提前取消,那么最好手動提前取消,從而可以快速釋放資源,同時需要注意的是 context 的取消操作針對的只是 context,如果還涉及到一些其他的操作,例如和數據庫通信、文件讀寫等,這些也需要我們手動取消。 以下幾點是使用 context 的一些約定俗成的建議: 1、不要將 context 塞到結構體里面,相反的它應該作為函數的第一個參數,并且統一命名成 ctx; 2、不要傳入一個 nil context,如果不知道傳啥,可以使用 context.TODO() 傳入一個 emptyCtx; 3、context 中存儲的應該是貫穿整個生命周期的數據,例如用戶的 session、cookie 等,不要把本應該作為函數參數的數據放進 context 中; 4、**key 的類型最好不要是字符串類型或者其它內建類型**,否則容易在包之間使用 Context 時候產生沖突。使用?`WithValue` 時,**key 的類型最好是自己定義的類型**; 5、**context 是天然并發安全的**,不需要擔心多個 goroutine 對它的并發操作。 ### 典型應用 #### 數據傳遞 ~~~ const KEY_LOG = "LOG_ID" const KEY_USER_ID = "USER_ID" func TestWithValue(t *testing.T) { // 通過 WithValue() 生成一個保存 key-value 鍵值對的 ctx ctx := context.WithValue(context.Background(), KEY_LOG, "2021082900001") // 鏈式存入第二個 key ctx = context.WithValue(ctx, KEY_USER_ID, "112233") logId := GetLogID(ctx) t.Log(logId) } func GetLogID(ctx context.Context) string { // 通過 Value() 方法查找 if logId := ctx.Value(KEY_LOG); logId != nil { return logId.(string) } return "" } ~~~ #### 取消協程執行 通過檢查 Context 的?`Done` 方法我們可以判斷它是否被 cancel 了。同時 context 還提供了兩個帶有超時功能的方法,分別是?`WithTimeout` 和?`WithDeadline` ,它們本質上是一樣的,只不過前者的入參是超時時間,后者的入參是截至的時間,通過這兩個方法生成的 ctx,都能夠實現在時間到了之后,自動執行?`cancel` 方法,當然我們也可以選擇(最好)在超時時間到來之前手動調用?`cancel` 。在很多微服務調用的實現場景,都是通過它們來實現遠程調用的超時控制的。 ~~~ func TestWithCancel(t *testing.T) { ctx, cancel := context.WithCancel(ctx) // WithTimeout 可以實現超時自動調用 Cancel() // ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) go TakeTasks(ctx, "c1") // ctx2 是 ctx 的子 context,當 ctx 被取消之后,ctx2 也會被取消 ctx2, _ := context.WithCancel(ctx) go TakeTasks(ctx2, "c2") time.Sleep(500 * time.Millisecond) // 也可以手動提前取消 cancel() time.Sleep(100 * time.Millisecond) } func cancelled(ctx context.Context) bool { select { case <-ctx.Done(): fmt.Println("finish taking tasks!") return true default: fmt.Println("continue!") return false } } func TakeTasks(ctx context.Context, flag string) { for { if cancelled(ctx) { break } fmt.Printf("%s taking tasks!\n", flag) time.Sleep(100 * time.Millisecond) } } ~~~ context 的取消操作是一層一層往下傳遞的。也就是說在調用?`cancel()` ?之后,對應的 ctx 會先標記自己已經被取消,然后它會向它的所有子 ctx 傳達取消信號,通知它們也應該被取消掉了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看