1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【Go進階—并發(fā)編程】Context

        共 10902字,需瀏覽 22分鐘

         ·

        2022-03-08 08:52

        作者:與昊

        來源:SegmentFault  思否社區(qū) 


        Context 是 Go 應用開發(fā)常用的并發(fā)控制技術(shù),它與 WaitGroup 最大的不同點是 Context 對于派生 goroutine 有更強的控制力,它可以控制多級的 goroutine。


        盡管有很多的爭議,但是在很多場景下使用 Context 都很方便,所以現(xiàn)在它已經(jīng)在 Go 生態(tài)圈中傳播開來了,包括很多的 Web 應用框架,都切換成了標準庫的 Context。標準庫中的 database/sql、os/exec、net、net/http 等包中都使用到了 Context。而且,如果遇到了下面的一些場景,也可以考慮使用 Context:


        • 上下文信息傳遞 ,比如處理 http 請求、在請求處理鏈路上傳遞信息;

        • 控制子 goroutine 的運行;

        • 超時控制的方法調(diào)用;

        • 可以取消的方法調(diào)用。


        實現(xiàn)原理



        接口定義


        包 context 定義了 Context 接口,Context 的具體實現(xiàn)包括 4 個方法,分別是 Deadline、Done、Err 和 Value,如下所示:

        type Context interface {
            Deadline() (deadline time.Time, ok bool)
            Done() <-chan struct{}
            Err() error
            Value(key interface{}) interface{}
        }

        Deadline 方法會返回這個 Context 被取消的截止日期。如果沒有設(shè)置截止日期,ok 的值是 false。后續(xù)每次調(diào)用這個對象的 Deadline 方法時,都會返回和第一次調(diào)用相同的結(jié)果。

        Done 方法返回一個 Channel 對象,基本上都會在 select 語句中使用。在 Context 被取消時,此 Channel 會被 close,如果沒被取消,可能會返回 nil。當 Done 被 close 的時候,可以通過 ctx.Err 獲取錯誤信息。

        關(guān)于 Err 方法,你必須要記住的知識點就是:如果 Done 沒有被 close,Err 方法返回 nil;如果 Done 被 close,Err 方法會返回 Done 被 close 的原因。

        Value 返回此 ctx 中和指定的 key 相關(guān)聯(lián)的 value。

        Context 中實現(xiàn)了 2 個常用的生成頂層 Context 的方法:

        • context.Background():返回一個非 nil 的、空的 Context,沒有任何值,不會被 cancel,不會超時,沒有截止日期。一般用在主函數(shù)、初始化、測試以及創(chuàng)建根 Context 的時候。

        • context.TODO():返回一個非 nil 的、空的 Context,沒有任何值,不會被 cancel,不會超時,沒有截止日期。當你不清楚是否該用 Context,或者目前還不知道要傳遞一些什么上下文信息的時候,就可以使用這個方法。


        事實上,它們兩個底層的實現(xiàn)是一模一樣的。絕大多數(shù)情況下可以直接使用 context.Background。

        在使用 Context 的時候,有一些約定俗成的規(guī)則:

        1. 一般函數(shù)使用 Context 的時候,會把這個參數(shù)放在第一個參數(shù)的位置。

        2. 從來不把 nil 當做 Context 類型的參數(shù)值,可以使用 context.Background() 創(chuàng)建一個空的上下文對象,也不要使用 nil。

        3. Context 只用來臨時做函數(shù)之間的上下文透傳,不能持久化 Context 或者把 Context 長久保存。把 Context 持久化到數(shù)據(jù)庫、本地文件或者全局變量、緩存中都是錯誤的用法。

        4. key 的類型不推薦字符串類型或者其它內(nèi)建類型,否則容易在包之間使用 Context 時候產(chǎn)生沖突。使用 WithValue 時,key 的類型應該是自己定義的類型。

        5. 常常使用 struct{} 作為底層類型定義 key 的類型。對于 exported key 的靜態(tài)類型,常常是接口或者指針。這樣可以盡量減少內(nèi)存分配。


        context 包中實現(xiàn) Context 接口的 struct,除了用于 context.Background() 的 emptyCtx 外,還有 cancelCtx、timerCtx 和 valueCtx 三種。

        cancelCtx

        type cancelCtx struct {
            Context

            mu       sync.Mutex            // 互斥鎖
            done     atomic.Value          // 調(diào)用 cancel 時會關(guān)閉的 channel
            children map[canceler]struct{} // 記錄了由此 Context 派生的所有 child,此 Context 被 cancel 時會同時 cancel 所有 child
            err      error                 // 錯誤信息
        }

        WithCancel

        cancelCtx 是通過 WithCancel 方法生成的。我們常常在一些需要主動取消長時間的任務(wù)時,創(chuàng)建這種類型的 Context,然后把這個 Context 傳給長時間執(zhí)行任務(wù)的 goroutine。當需要中止任務(wù)時,我們就可以 cancel 這個 Context,這樣長時間執(zhí)行任務(wù)的 goroutine,就可以通過檢查這個 Context,知道 Context 已經(jīng)被取消了。

        WithCancel 返回值中的第二個值是一個 cancel 函數(shù)。記住,不是只有你想中途放棄,才去調(diào)用 cancel,只要你的任務(wù)正常完成了,就需要調(diào)用 cancel,這樣,這個 Context 才能釋放它的資源(通知它的 children 處理 cancel,從它的 parent 中把自己移除,甚至釋放相關(guān)的 goroutine)。

        看下核心源碼:

        func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
            if parent == nil {
                panic("cannot create context from nil parent")
            }
            c := newCancelCtx(parent)
            propagateCancel(parent, &c)
            return &c, func() { c.cancel(true, Canceled) }
        }

        func newCancelCtx(parent Context) cancelCtx {
            return cancelCtx{Context: parent}
        }

        func propagateCancel(parent Context, child canceler) {
            done := parent.Done()

            if p, ok := parentCancelCtx(parent); ok {
                p.mu.Lock()
                if p.err != nil {
                    // parent 已經(jīng)取消了,直接取消子 Context
                    child.cancel(false, p.err)
                } else {
                    // 將 child 添加到 parent 的 children 切片
                    if p.children == nil {
                        p.children = make(map[canceler]struct{})
                    }
                    p.children[child] = struct{}{}
                }
                p.mu.Unlock()
            } else {
                atomic.AddInt32(&goroutines, +1)
                // 沒有 parent 可以“掛載”,啟動一個 goroutine 監(jiān)聽 parent 的 cancel,同時 cancel 自身
                go func() {
                    select {
                    case <-parent.Done():
                        child.cancel(false, parent.Err())
                    case <-child.Done():
                    }
                }()
            }
        }

        代碼中調(diào)用的 propagateCancel 方法會順著 parent 路徑往上找,直到找到一個 cancelCtx,或者為 nil。如果不為空,就把自己加入到這個 cancelCtx 的 child,以便這個 cancelCtx 被取消的時候通知自己。如果為空,會新起一個 goroutine,由它來監(jiān)聽 parent 的 Done 是否已關(guān)閉。

        當這個 cancelCtx 的 cancel 函數(shù)被調(diào)用的時候,或者 parent 的 Done 被 close 的時候,這個 cancelCtx 的 Done 才會被 close。

        cancel 是向下傳遞的,如果一個 WithCancel 生成的 Context 被 cancel 時,如果它的子 Context(也有可能是孫,或者更低,依賴子的類型)也是 cancelCtx 類型的,就會被 cancel。

        cancel

        cancel 方法的作用是 close 自己及其后代的 done 通道,達到通知取消的目的。WithCancel 方法的第二個返回值 cancel 就是本函數(shù)。來看一下主要代碼實現(xiàn):

        func (c *cancelCtx) cancel(removeFromParent bool, err error) {
            c.mu.Lock()
            // 設(shè)置 cancel 的原因
            c.err = err 
            // 關(guān)閉自身的 done 通道
            d, _ := c.done.Load().(chan struct{})
            if d == nil {
                c.done.Store(closedchan)
            } else {
                close(d)
            }
            // 遍歷所有 children,逐個調(diào)用 cancel 方法
            for child := range c.children {
                child.cancel(false, err)
            }
            c.children = nil
            c.mu.Unlock()

            // 正常情況下,需要將自身從 parent 的 children 切片中刪除
            if removeFromParent {
                removeChild(c.Context, c)
            }
        }

        timerCtx


        type timerCtx struct {
            cancelCtx
            timer *time.Timer 

            deadline time.Time
        }

        timerCtx 在 cancelCtx 基礎(chǔ)上增加了 deadline 用于標示自動 cancel 的最終時間,而 timer 就是一個觸發(fā)自動 cancel 的定時器。timerCtx 可以由 WithDeadline 和 WithTimeout 生成, WithTimeout 實際調(diào)用了 WithDeadline,二者實現(xiàn)原理一致,只不過使用語境不一樣:WithDeadline 是指定最后期限,WithTimeout 是指定最長存活時間。

        WithDeadline

        來看一下 WithDeadline 方法的實現(xiàn):

        func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
            // 如果parent的截止時間更早,直接返回一個cancelCtx即可
            if cur, ok := parent.Deadline(); ok && cur.Before(d) {
                return WithCancel(parent)
            }
            c := &timerCtx{
                cancelCtx: newCancelCtx(parent),
                deadline:  d,
            }
            propagateCancel(parent, c) // 同cancelCtx的處理邏輯
            dur := time.Until(d)
            if dur <= 0 { //當前時間已經(jīng)超過了截止時間,直接cancel
                c.cancel(true, DeadlineExceeded)
                return c, func() { c.cancel(false, Canceled) }
            }
            c.mu.Lock()
            defer c.mu.Unlock()
            if c.err == nil {
                // 設(shè)置一個定時器,到截止時間后取消
                c.timer = time.AfterFunc(dur, func() {
                    c.cancel(true, DeadlineExceeded)
                })
            }
            return c, func() { c.cancel(true, Canceled) }
        }

        WithDeadline 會返回一個 parent 的副本,并且設(shè)置了一個不晚于參數(shù) d 的截止時間,類型為 timerCtx(或者是 cancelCtx)。

        如果它的截止時間晚于 parent 的截止時間,那么就以 parent 的截止時間為準,并返回一個類型為 cancelCtx 的 Context,因為 parent 的截止時間到了,就會取消這個 cancelCtx。如果當前時間已經(jīng)超過了截止時間,就直接返回一個已經(jīng)被 cancel 的 timerCtx。否則就會啟動一個定時器,到截止時間取消這個 timerCtx。

        綜合起來,timerCtx 的 Done 被 Close 掉,主要是由下面的某個事件觸發(fā)的:

        • 截止時間到了;

        • cancel 函數(shù)被調(diào)用;

        • parent 的 Done 被 close。


        和 cancelCtx 一樣,WithDeadline(WithTimeout)返回的 cancel 一定要調(diào)用,并且要盡可能早地被調(diào)用,這樣才能盡早釋放資源,不要單純地依賴截止時間被動取消。

        valueCtx


        type valueCtx struct {
            Context
            key, val interface{}
        }

        valueCtx 只是在 Context 基礎(chǔ)上增加了一個 key-value 對,用于在各級協(xié)程間傳遞一些數(shù)據(jù)。

        WithValue 基于 parent Context 生成一個新的 valueCtx,保存了一個 key-value 鍵值對。valueCtx 覆蓋了 Value 方法,優(yōu)先從自己的存儲中檢查這個 key,不存在的話會從 parent 中繼續(xù)檢查。



        點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

        - END -


        瀏覽 58
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产亚洲精品成人a| 精品国产91乱码一区二区三区| 综合一区二区三区| 伊人大香蕉综合在线| 91色区| 久天堂| 一级黄片免费| 无码三级在线播放| 91福利资源| 婷婷九月色| 欧美淫秽视频| 久久婷婷五月| 男女操逼网站| 亚洲成人免费| 久久草在线观看| 国产肏屄视频| 无码AV一区二区| 日韩成人无码电影| 丰满人妻精品一区二区在线| 中文无码日本一级A片人| 中文字幕成人无码| 亚洲中文字幕视频在线| 中文字幕一区二区三区在线观看| 91精品婷婷国产综合久久蝌蚪| 日本高清久久| 国产成人片在线观看| 2017天天干天天射| www.啪| 日本AⅤ中文字幕| 亚洲AV片一区二区三区| 人人摸人人操人人爽| 大地8免费高清视频观看大全| 91羞羞网站| 国产欧美在线不卡| 欧美在线观看一区| youjizzcom日本| 久久亚洲中文字幕乱码| 久久精品在线| 日韩性爱视频| 影音先锋成人| 你懂的在线视频| 97精品视频| 超碰在线免费| 日韩TV| 日韩精品一区二区三区免费观看高清 | 麻豆网站91| 无码在线看| 玖玖资源在线观看| 99热999| 99久久人妻无码中文字幕系列| 91亚洲精华国产精华精华液| 色噜噜狠狠一区二区三区| 黄色不卡视频| 久久久久黄| 中文字幕丰满的翔田千里| 91成人一区二区| 亚洲国产一| 青青草原免费在线视频| 一区二区三区无码精品| 日B免费视频| 91久久久久久久18| 日本亚洲中文字幕| 色色五月丁香婷婷| 亚洲AV无码精品国产| 黄色精品网站| 丁香社区五月天| 99视频这里有精品| 少妇搡BBBB搡BBB搡造水多| 色综合天天综合网国产成人网| 欧美精品18| 久久久久伊人| 九九成人电影| 日本午夜福利电影| 国产内射在线观看| 18禁在线播放| 国产精品一二三| 懂色AV| 黑人vs亚洲人在线播放| a片视频免费观看| 超碰在线日韩| 99视频免费在线| 精品无人区无码乱码毛片国产| 天天日天天操天天爽| 尤物网站在线观看| 二区精品| 国产成人午夜精品无码区久久麻豆| wwwsesese| 精品无码人妻一区二区| 制服乱伦| 国产理论片| 日韩黄色在线观看| 超碰9| 高清一区二区| 久久免费操| 狠狠撸狠狠撸| 好看的中文字幕av| 国产SM视频| 72成人网| 曰曰摸日日碰| 亚洲不卡在线观看| 日韩欧美分区视频| 欧美性爱免费网站| 黄片视频免费在线观看| 日本黄色电影网站| 精品乱码一区| 777偷窥盗摄00000| 最新中文字幕AV| 伊人大香在线| 怡红院成人av| 十八禁黄网站| 亲子伦一区二区三区| 亚洲精品在线观看视频| 中文字幕伊人| 懂色AV| 国产国产国产在线无码视频| 中文字幕亚洲人妻| 艹逼无码| 亚洲色热| 国产迷奸视频| 国产一级a毛一级a做免费高清视频| 东北嫖老熟女一区二区视频网站| 亚洲在线免费| 内射自拍| av日韩在线播放| 黄色A毛片| 91看片看婬黄大片女跟女| 日韩一级黄色毛片| 久久aaa| 悠悠色综合| 无码人妻一区二区三区四区老鸭窝| 黄色电影免费网站| 人与鲁牲交| 国产精品国产三级国产| 久久国产一区二区三区| 国产精品s色| 人人操大香蕉| 嫩草av| 久久嫩草精品久久久久精| 国产精品国内自产| 免费人成在线观看视频播放 | 亚洲日韩乱码在线| 国产高清精品在线| 日本黄A三级三级三级| 午夜成人中文字幕| 色爽AV| 女人的天堂av| 色色97| 欧美成人中文字幕| 国产伦精品一级A片视频夜夜| 无码人妻熟妇| 可以在线观看的av| 香蕉伊人在线| 美女网站黄| 日狠狠| 国产真实乱婬A片久久久老牛| 天天躁夜夜躁狠狠躁AV| 丰满岳乱妇一区二区三区| 色五月婷婷中文字幕| 中文字幕乱码亚洲中文在线| 国产寡妇亲子伦一区二区三区四区| 日本中文字幕视频| 久久婷婷五月综合伊人| 日韩中文字幕人妻| 午夜精品久久久久久久99黑人| 黄色视频在线免费观看高清视频 | 99久久精品国产一区二区三区| 无码一区二区三区四区五区六区 | 蜜桃91视频| 亚洲成人网站在线观看| 人妻熟女视频| 青青草原国产视频| 久久久精品午夜人成欧洲亚洲韩国 | 亚洲欧洲精品成人久久曰影片| 日韩色逼| 91麻豆精品国产91久久久吃药| 白浆在线| 伊人五月在线| 午夜成人无码视频| 中文字幕av免费观看| 北条麻妃无码播放| a√天堂中文8| 久久人人网| 国产xxxx视频| 国产精品啪啪视频| 91丨人妻丨偷拍| 韩日一级17c| 国产精品一区二区AV日韩在线| 夜夜骚av.一区二区三区四区| 五月丁香999| 亚洲无码视频免费看| 2019中文字幕在线| 欧美视频在线观看一区| 亚洲无吗在线观看| 成人视频在线观看免费| 在线观看无码视频| 国产1024在线| 一级少女免费播放电视剧韩剧TV| 激情五月在线| 91视频网站在线观看| 91蜜桃传媒| 亚洲精品二| 簧片网站在线观看| 久久久三级| 色福利网| 欧洲三级网观看| 色色色色五月天| 最好看的MV中文字幕国语电影| 大香蕉伊人成人网| 丁香五月婷婷基地| 99热er| 91精品久久久久久久| 国产精品AV网站| 国产一区二三区| 麻豆AV在线观看| 国产激情在线视频| 欧美性爱免费在线视频| 国产九九热视频| 日韩熟妇无码中文字幕| 国产一级婬女AAAA片季秀英| 黄色搞逼视频| 无码精品一区二区在线| 国产一级a爱做片免费☆观看| 日韩天堂av| 国产一区久久| 91精品丝袜久久久久久久久粉嫩| 国产美女高潮| 2025天天操夜夜操| 精产国品一区二区三区| 日韩少妇视频| 欧美在线不卡综合| 国产黄在线| 西西444WWW无码精品| 国产黄色在线视频| 国产精品3| 久久久久99精品成人片三人毛片| 91在线无码精品秘| 激情丁香| 久久肏| 三须三级久久三级久久18| 婷婷精品免费久久| 51黄片库| 青青草黄色片| 五月婷婷精品| 欧美熟妇精品黑人巨大一二三区| 日韩aaa| 国产精品一区二区三区不卡| 91丝袜一区在线观看| 亚洲无码三区| 成人色色视频| 五月婷在线观看| 99成人国产精品视频| 亚洲精品欧美久久婷婷| 亚洲午夜激情电影| 狠狠操在线观看| 亚洲黄色视频网站| 先锋成人影音| 国产丝袜人妖TS系列| 中文天堂网| 国产真人一级a爱做片| 精品视频日韩| 无码AⅤ一区二区三区| gogogo高清在线观看免费直播中国| 五月天操逼网站| 伊人无码在线| 青青草av| 波多野结衣视频无码| 中文一区| 大香蕉啪啪| 2024天天操| 五月天婷婷视频| 西西444WWW无码视频软件功能介绍 | 中文字幕一区二区三区四区| 日韩AV免费在线播放| 水蜜桃视频在线| 国产91无码网站在线观看| 屁屁影院国产第一页| 色欲影视插综合一区二区三区| 色综合国产| 亚洲国产日本| 亚州天堂| 亚洲天堂2025| 欧美在线视频免费观看| 亚洲乱码精品久久久久..| 波多野结衣无码高清视频| 老熟妇一区二区三区啪啪| 国产成人无码AⅤ片免费播放| 国产黄色视频免费看| 丝袜一区二区三区| www久久99| 久久aaaa| 亚洲男人的天堂网| 日韩成人无码免费视频| 亚洲撸撸| 国产色视频一区二区三区QQ号| 亚洲无套内射| 免费一级A毛片夜夜看| 成人a片在线观看| 99热这里只有精品1| a在线免费| 二区视频|