最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言學習之context包的用法詳解

 更新時間:2022年10月09日 14:17:58   作者:漫漫Coding路  
日常Go開發(fā)中,Context包是用的最多的一個了,幾乎所有函數(shù)的第一個參數(shù)都是ctx,那么我們?yōu)槭裁匆獋鬟fContext呢,Context又有哪些用法,底層實現(xiàn)是如何呢?相信你也一定會有探索的欲望,那么就跟著本篇文章,一起來學習吧

前言

日常 Go 開發(fā)中,Context 包是用的最多的一個了,幾乎所有函數(shù)的第一個參數(shù)都是 ctx,那么我們?yōu)槭裁匆獋鬟f Context 呢,Context 又有哪些用法,底層實現(xiàn)是如何呢?相信你也一定會有探索的欲望,那么就跟著本篇文章,一起來學習吧!

需求一

開發(fā)中肯定會調用別的函數(shù),比如 A 調用 B,在調用過程中經(jīng)常會設置超時時間,比如超過2s 就不等待 B 的結果了,直接返回,那么我們需要怎么做呢?

//?睡眠5s,模擬長時間操作
func?FuncB()?(interface{},?error)?{
?time.Sleep(5?*?time.Second)
?return?struct{}{},?nil
}

func?FuncA()?(interface{},?error)?{

?var?res?interface{}
?var?err?error
?ch?:=?make(chan?interface{})

??//?調用FuncB(),將結果保存至?channel?中
?go?func()?{
??res,?err?=?FuncB()
??ch?<-?res
?}()

??//?設置一個2s的定時器
?timer?:=?time.NewTimer(2?*?time.Second)
??
??//?監(jiān)測是定時器先結束,還是?FuncB?先返回結果
?select?{
????
????//?超時,返回默認值
?case?<-timer.C:
??return?"default",?err
????
????//?FuncB?先返回結果,關閉定時器,返回?FuncB?的結果
?case?r?:=?<-ch:
??if?!timer.Stop()?{
???<-timer.C
??}
??return?r,?err
?}

}

func?main()?{
?res,?err?:=?FuncA()
?fmt.Println(res,?err)
}

上面我們的實現(xiàn),可以實現(xiàn)超過等待時間后,A 不等待 B,但是 B 并沒有感受到取消信號,如果 B 是個計算密度型的函數(shù),我們也希望B 感知到取消信號,及時取消計算并返回,減少資源浪費。

另一種情況,如果存在多層調用,比如A 調用 B、C,B 調用 D、E,C調用 E、F,在超過 A 的超時時間后,我們希望取消信號能夠一層層的傳遞下去,后續(xù)所有被調用到的函數(shù)都能感知到,及時返回。

需求二

在多層調用的時候,A->B->C->D,有些數(shù)據(jù)需要固定傳輸,比如 LogID,通過打印相同的 LogID,我們就能夠追溯某一次調用,方便問題的排查。如果每次都需要傳參的話,未免太麻煩了,我們可以使用 Context 來保存。通過設置一個固定的 Key,打印日志時從中取出 value 作為 LogID。

const?LogKey?=?"LogKey"

//?模擬一個日志打印,每次從?Context?中取出?LogKey?對應的?Value?作為LogID
type?Logger?struct{}
func?(logger?*Logger)?info(ctx?context.Context,?msg?string)?{
?logId,?ok?:=?ctx.Value(LogKey).(string)
?if?!ok?{
??logId?=?uuid.New().String()
?}
?fmt.Println(logId?+?"?"?+?msg)
}
var?logger?Logger

//?日志打印?并?調用?FuncB
func?FuncA(ctx?context.Context)?{
?logger.info(ctx,?"FuncA")
?FuncB(ctx)
}

func?FuncB(ctx?context.Context)?{
?logger.info(ctx,?"FuncB")
}

//?獲取初始化的,帶有?LogID?的?Context,一般在程序入口做
func?getLogCtx(ctx?context.Context)?context.Context?{
?logId,?ok?:=?ctx.Value(LogKey).(string)
?if?ok?{
??return?ctx
?}
?logId?=?uuid.NewString()
?return?context.WithValue(ctx,?LogKey,?logId)
}

func?main()?{
?ctx?=?getLogCtx(context.Background())
?FuncA(ctx)
}

這利用到了本篇文章講到的 valueCtx,繼續(xù)往下看,一起來學習 valueCtx 是怎么實現(xiàn)的吧!

Context 接口

type?Context?interface?{

?Deadline()?(deadline?time.Time,?ok?bool)

?Done()?<-chan?struct{}

?Err()?error

?Value(key?interface{})?interface{}
}

Context 接口比較簡單,定義了四個方法:

  • Deadline() 方法返回兩個值,deadline 表示 Context 將會在什么時間點取消,ok 表示是否設置了deadline。當 ok=false 時,表示沒有設置deadline,那么此時 deadline 將會是個零值。多次調用這個方法返回同樣的結果。
  • Done() 返回一個只讀的 channel,類型為 chan struct{},如果當前的 Context 不支持取消,Done 返回 nil。我們知道,如果一個 channel 中沒有數(shù)據(jù),讀取數(shù)據(jù)會阻塞;而如果channel被關閉,則可以讀取到數(shù)據(jù),因此可以監(jiān)聽 Done 返回的 channel,來獲取 Context 取消的信號。
  • Err() 返回 Done 返回的 channel 被關閉的原因。當 channel 未被關閉時,Err() 返回 nil;channel 被關閉時則返回相應的值,比如 Canceled 、DeadlineExceeded。Err() 返回一個非 nil 值之后,后面再次調用會返回相同的值。
  • Value() 返回 Context 保存的鍵值對中,key 對應的 value,如果 key 不存在則返回 nil。

Done() 是一個比較常用的方法,下面是一個比較經(jīng)典的流式處理任務的示例:監(jiān)聽 ctx.Done() 是否被關閉來判斷任務是否需要取消,需要取消則返回相應的原因;沒有取消則將計算的結果寫入到 out channel中。

?func?Stream(ctx?context.Context,?out?chan<-?Value)?error?{
??for?{
????
????//?處理數(shù)據(jù)
???v,?err?:=?DoSomething(ctx)
???if?err?!=?nil?{
????return?err
???}
????
????//?ctx.Done()?讀取到數(shù)據(jù),說明獲取到了任務取消的信號
???select?{
???case?<-ctx.Done():
????return?ctx.Err()
????//?否則將結果輸出,繼續(xù)計算
???case?out?<-?v:
???}
??}
?}

Value() 也是一個比較常用的方法,用于在上下文中傳遞一些數(shù)據(jù)。使用 context.WithValue() 方法存入 key 和 value,通過 Value() 方法則可以根據(jù) key 拿到 value。

func?main()?{
?ctx?:=?context.Background()
?c?:=?context.WithValue(ctx,?"key",?"value")
?v,?ok?:=?c.Value("key").(string)
?fmt.Println(v,?ok)
}

emptyCtx

Context 接口并不需要我們自己去手動實現(xiàn),一般我們都是直接使用 context 包中提供的 Background() 方法和 TODO() 方法,來獲取最基礎的 Context。

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

func?Background()?Context?{
?return?background
}

func?TODO()?Context?{
?return?todo
}

Background() 方法一般用在 main 函數(shù),或者程序的初始化方法中;在我們不知道使用哪個 Context,或者上文沒有傳遞 Context時,可以使用 TODO()。

Background() 和 TODO() 都是基于 emptyCtx 生成的,從名字可以看出來,emptyCtx 是一個空的Context,沒有 deadline、不能被取消、沒有鍵值對。

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
}

func?(e?*emptyCtx)?String()?string?{
?switch?e?{
?case?background:
??return?"context.Background"
?case?todo:
??return?"context.TODO"
?}
?return?"unknown?empty?Context"
}

除了上面兩個最基本的 Context 外,context 包中提供了功能更加豐富的 Context,包括 valueCtx、cancelCtx、timerCtx,下面我們就挨個來看下。

valueCtx

使用示例

我們一般使用 context.WithValue() 方法向 Context 存入鍵值對,然后通過 Value() 方法根據(jù) key 得到 value,此種功能的實現(xiàn)就依賴 valueCtx。

func?main()?{
?ctx?:=?context.Background()
?c?:=?context.WithValue(ctx,?"myKey",?"myValue")

?v1?:=?c.Value("myKey")
?fmt.Println(v1.(string))

?v2?:=?c.Value("hello")
?fmt.Println(v2)?//??nil
}

類型定義

valueCtx 結構體中嵌套了 Context,使用 key 、value 來保存鍵值對:

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

WithValue

context包 對外暴露了 WithValue 方法,基于一個 parent context 來創(chuàng)建一個 valueCtx。從下面的源碼中可以看出,key 必須是可比較的!

func?WithValue(parent?Context,?key,?val?interface{})?Context?{
?if?parent?==?nil?{
??panic("cannot?create?context?from?nil?parent")
?}
?if?key?==?nil?{
??panic("nil?key")
?}
?if?!reflectlite.TypeOf(key).Comparable()?{
??panic("key?is?not?comparable")
?}
?return?&valueCtx{parent,?key,?val}
}

*valueCtx 實現(xiàn)了 Value(),可以根據(jù) key 得到 value。這是一個向上遞歸尋找的過程,如果 key 不在當前 valueCtx 中,會繼續(xù)向上找 parent Context,直到找到最頂層的 Context,一般最頂層的是 emptyCtx,而 emtpyCtx.Value() 返回 nil。

func?(c?*valueCtx)?Value(key?interface{})?interface{}?{
?if?c.key?==?key?{
??return?c.val
?}
?return?c.Context.Value(key)
}

cancelCtx

cancelCtx 是一個用于取消任務的 Context,任務通過監(jiān)聽 Context 是否被取消,來決定是否繼續(xù)處理任務還是直接返回。

如下示例中,我們在 main 函數(shù)定義了一個 cancelCtx,并在 2s 后調用 cancel() 取消 Context,即我們希望 doSomething() 在 2s 內完成任務,否則就可以直接返回,不需要再繼續(xù)計算浪費資源了。

doSomething() 方法內部,我們使用 select 監(jiān)聽任務是否完成,以及 Context 是否已經(jīng)取消,哪個先到就執(zhí)行哪個分支。方法模擬了一個 5s 的任務,main 函數(shù)等待時間是2s,因此沒有完成任務;如果main函數(shù)等待時間改為10s,則任務完成并會返回結果。

這只是一層調用,真實情況下可能會有多級調用,比如 doSomething 可能又會調用其他任務,一旦 parent Context 取消,后續(xù)的所有任務都應該取消。

func?doSomething(ctx?context.Context)?(interface{},?error)?{
?res?:=?make(chan?interface{})
?go?func()?{
??fmt.Println("do?something")
??time.Sleep(time.Second?*?5)
??res?<-?"done"
?}()

?select?{
?case?<-ctx.Done():
??return?nil,?ctx.Err()
?case?value?:=?<-res:
??return?value,?nil
?}
}

func?main()?{
?ctx,?cancel?:=?context.WithCancel(context.Background())
?go?func()?{
??time.Sleep(time.Second?*?2)
??cancel()
?}()
?res,?err?:=?doSomething(ctx)
?fmt.Println(res,?err)?//?nil?,?context?canceled
}

接下來就讓我們來研究下,cancelCtx 是如何實現(xiàn)取消的吧

類型定義

  • canceler 接口包含 cancel() 和 Done() 方法,*cancelCtx 和 *timerCtx 均實現(xiàn)了這個接口。
  • closedchan 是一個被關閉的channel,可以用于后面 Done() 返回
  • canceled 是一個 err,用于 Context 被取消的原因
type?canceler?interface?{
?cancel(removeFromParent?bool,?err?error)
?Done()?<-chan?struct{}
}

//?closedchan?is?a?reusable?closed?channel.
var?closedchan?=?make(chan?struct{})

func?init()?{
?close(closedchan)
}

var?Canceled?=?errors.New("context?canceled")

CancelFunc 是一個函數(shù)類型定義,是一個取消函數(shù),有如下規(guī)范:

  • CancelFunc 告訴一個任務停止工作
  • CancelFunc 不會等待任務結束
  • CancelFunc 支持并發(fā)調用
  • 第一次調用后,后續(xù)的調用不會產生任何效果
type?CancelFunc?func()

&cancelCtxKey 是一個固定的key,用來返回 cancelCtx 自身

var?cancelCtxKey?int

cancelCtx

cancelCtx 是可以被取消的,它嵌套了 Context 接口,實現(xiàn)了 canceler 接口。cancelCtx 使用 children 字段保存同樣實現(xiàn) canceler 接口的子節(jié)點,當 cancelCtx 被取消時,所有的子節(jié)點也會取消。

type?cancelCtx?struct?{
?Context

?mu???????sync.Mutex????????????//?保護如下字段,保證線程安全
?done?????atomic.Value??????????//?保存?channel,懶加載,調用?cancel?方法時會關閉這個?channel
?children?map[canceler]struct{}?//?保存子節(jié)點,第一次調用?cancel?方法時會置為?nil
?err??????error?????????????????//?保存為什么被取消,默認為nil,第一次調用?cancel?會賦值
}

*cancelCtx 的 Value() 方法 和 *valueCtx 的 Value() 方法類似,只不過加了個固定的key: &cancelCtxKey。當key 為 &cancelCtxKey 時返回自身

func?(c?*cancelCtx)?Value(key?interface{})?interface{}?{
?if?key?==?&cancelCtxKey?{
??return?c
?}
?return?c.Context.Value(key)
}

*cancelCtx 的 done 字段是懶加載的,只有在調用 Done() 方法 或者 cancel() 時才會賦值。

func?(c?*cancelCtx)?Done()?<-chan?struct{}?{
?d?:=?c.done.Load()
??
??//?如果已經(jīng)有值了,直接返回
?if?d?!=?nil?{
??return?d.(chan?struct{})
?}
??
??//?沒有值,加鎖賦值
?c.mu.Lock()
?defer?c.mu.Unlock()
?d?=?c.done.Load()
?if?d?==?nil?{
??d?=?make(chan?struct{})
??c.done.Store(d)
?}
?return?d.(chan?struct{})
}

Err 方法返回 cancelCtx 的 err 字段

func?(c?*cancelCtx)?Err()?error?{
???c.mu.Lock()
???err?:=?c.err
???c.mu.Unlock()
???return?err
}

WithCancel

那么我們如何新建一個 cancelCtx呢?context 包提供了 WithCancel() 方法,讓我們基于一個 Context 來創(chuàng)建一個 cancelCtx。WithCancel() 方法返回兩個字段,一個是基于傳入的 Context 生成的 cancelCtx,另一個是 CancelFunc。

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)?}
}

WithCancel 調用了兩個外部方法:newCancelCtx 、propagateCancel。newCancelCtx 比較簡單,根據(jù)傳入的 context,返回了一個 cancelCtx 結構體。

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

propagateCancel 從名字可以看出,就是將 cancel 傳播。如果父Context支持取消,那么我們需要建立一個通知機制,這樣父節(jié)點取消的時候,通知子節(jié)點也取消,層層傳播。

在 propagateCancel 中,如果 父Context 是 cancelCtx 類型且未取消,會將 子Context 掛在它下面,形成一個樹結構;其余情況都不會掛載。

func?propagateCancel(parent?Context,?child?canceler)?{
??
??//?如果?parent?不支持取消,那么就不支持取消傳播,直接返回
?done?:=?parent.Done()
?if?done?==?nil?{
??return?
?}

??//?到這里說明?done?不為?nil,parent?支持取消
??
?select?{
?case?<-done:
??//?如果?parent?此時已經(jīng)取消了,那么直接告訴子節(jié)點也取消
??child.cancel(false,?parent.Err())
??return
?default:
?}

??//?到這里說明此時?parent?還未取消
??
??//?如果?parent?是未取消的?cancelCtx?
?if?p,?ok?:=?parentCancelCtx(parent);?ok?{
????
????//?加鎖,防止并發(fā)更新
??p.mu.Lock()
????
????//?再次判斷,因為有可能上一個獲得鎖的進行了取消操作。
????//?如果?parent?已經(jīng)取消了,那么子節(jié)點也直接取消
??if?p.err?!=?nil?{
???child.cancel(false,?p.err)
??}?else?{
??????//?把子Context?掛到父節(jié)點?parent?cancelCtx?的?children字段下
??????//?之后?parent?cancelCtx?取消時,能通知到所有的?子Context?
???if?p.children?==?nil?{
????p.children?=?make(map[canceler]struct{})
???}
???p.children[child]?=?struct{}{}
??}
??p.mu.Unlock()
?}?else?{
????
???//?parent?不是?cancelCtx?類型,可能是用戶自己實現(xiàn)的Context
??atomic.AddInt32(&goroutines,?+1)
????//?啟動一個協(xié)程監(jiān)聽,如果?parent?取消了,子?Context?也取消
??go?func()?{
???select?{
???case?<-parent.Done():
????child.cancel(false,?parent.Err())
???case?<-child.Done():
???}
??}()
?}
}

cancel 方法就是來取消 cancelCtx,主要的工作是:關閉c.done 中的channel,給 err 賦值,然后級聯(lián)取消所有 子Context。如果 removeFromParent 為 true,會從父節(jié)點中刪除以該節(jié)點為樹頂?shù)臉洹?/p>

cancel() 方法只負責自己管轄的范圍,即自己以及自己的子節(jié)點,然后根據(jù)配置判斷是否需要從父節(jié)點中移除自己為頂點的樹。如果子節(jié)點還有子節(jié)點,那么由子節(jié)點負責處理,不用自己負責了。

propagateCancel() 中有三處調用了 cancel() 方法,傳入的 removeFromParent 都為 false,是因為當時根本沒有掛載,不需要移除。而 WithCancel 返回的 CancelFunc ,傳入的 removeFromParent 為 true,是因為調用 propagateCancel 有可能產生掛載,當產生掛載時,調用 cancel() 就需要移除了。

func?(c?*cancelCtx)?cancel(removeFromParent?bool,?err?error)?{
??
??//?err?是指取消的原因,必傳,cancelCtx?中是?errors.New("context?canceled")
?if?err?==?nil?{
??panic("context:?internal?error:?missing?cancel?error")
?}
??
??//?涉及到保護字段值的修改,都需要加鎖
?c.mu.Lock()
??
??//?如果該Context已經(jīng)取消過了,直接返回。多次調用cancel,不會產生額外效果
?if?c.err?!=?nil?{
??c.mu.Unlock()
??return?
?}
??
??//?給?err?賦值,這里?err?一定不為?nil
?c.err?=?err
??
??//?close?channel
?d,?_?:=?c.done.Load().(chan?struct{})
??//?因為c.done?是懶加載,有可能存在?nil?的情況
??//?如果?c.done?中沒有值,直接賦值?closedchan;否則直接?close
?if?d?==?nil?{
??c.done.Store(closedchan)
?}?else?{
??close(d)
?}
??
??//?遍歷當前?cancelCtx?所有的子Context,讓子節(jié)點也?cancel
??//?因為當前的Context?會主動把子Context移除,子Context?不用主動從parent中脫離
??//?因此?child.cancel?傳入的?removeFromParent?為false
?for?child?:=?range?c.children?{
??child.cancel(false,?err)
?}
??//?將?children?置空,相當于移除自己的所有子Context
?c.children?=?nil
?c.mu.Unlock()
?
??//?如果當前?cancelCtx?需要從上層的?cancelCtx移除,調用removeChild方法
??//?c.Context?就是自己的父Context
?if?removeFromParent?{
??removeChild(c.Context,?c)
?}
}

從propagateCancel方法中可以看到,只有parent 屬于 cancelCtx 類型 ,才會將自己掛載。因此 removeChild 會再次判斷 parent 是否為 cancelCtx,和之前的邏輯保持一致。找到的話,再將自己移除,需要注意的是,移除會把自己及其自己下面的所有子節(jié)點都移除。

如果上一步 propagateCancel 方法將自己掛載到了 A 上,但是在調用 cancel() 時,A 已經(jīng)取消過了,此時 parentCancelCtx() 會返回 false。不過這沒有關系,A 取消時已經(jīng)將掛載的子節(jié)點移除了,當前的子節(jié)點不用將自己從 A 中移除了。

func?removeChild(parent?Context,?child?canceler)?{
??//?parent?是否為未取消的?cancelCtx
?p,?ok?:=?parentCancelCtx(parent)
?if?!ok?{
??return
?}
??//?獲取?parent?cancelCtx?的鎖,修改保護字段?children
?p.mu.Lock()
??//?將自己從?parent?cancelCtx?的?children?中刪除
?if?p.children?!=?nil?{
??delete(p.children,?child)
?}
?p.mu.Unlock()
}

parentCancelCtx 判斷 parent 是否為 未取消的 *cancelCtx。取消與否容易判斷,難判斷的是 parent 是否為  *cancelCtx,因為有可能其他結構體內嵌了 cancelCtx,比如 timerCtx,會通過比對 channel 來確定。

func?parentCancelCtx(parent?Context)?(*cancelCtx,?bool)?{
??
??//?如果?parent?context?的?done?為?nil,?說明不支持?cancel,那么就不可能是?cancelCtx
?//?如果?parent?context?的?done?為?closedchan,?說明?parent?context?已經(jīng)?cancel?了
?done?:=?parent.Done()
?if?done?==?closedchan?||?done?==?nil?{
??return?nil,?false
?}
??
??//?到這里說明支持取消,且沒有被取消
??
?//?如果?parent?context?屬于原生的?*cancelCtx?或衍生類型,需要繼續(xù)進行后續(xù)判斷
?//?如果?parent?context?無法轉換到?*cancelCtx,則認為非?cancelCtx,返回?nil,fasle
?p,?ok?:=?parent.Value(&cancelCtxKey).(*cancelCtx)
?if?!ok?{
??return?nil,?false
?}
?
??//?經(jīng)過上面的判斷后,說明?parent?context?可以被轉換為?*cancelCtx,這時存在多種情況:
?//???-?parent?context?就是?*cancelCtx
?//???-?parent?context?是標準庫中的?timerCtx
?//???-?parent?context?是個自己自定義包裝的?cancelCtx
?//
?//?針對這?3?種情況需要進行判斷,判斷方法就是:?
?//???判斷?parent?context?通過?Done()?方法獲取的?done?channel?與?Value?查找到的?context?的?done?channel?是否一致
?//?
?//?一致情況說明?parent?context?為?cancelCtx?或?timerCtx?或?自定義的?cancelCtx?且未重寫?Done(),
?//?這種情況下可以認為拿到了底層的?*cancelCtx
?//?
?//?不一致情況說明?parent?context?是一個自定義的?cancelCtx?且重寫了?Done()?方法,并且并未返回標準?*cancelCtx?的
?//?的?done?channel,這種情況需要單獨處理,故返回?nil,?false
?pdone,?_?:=?p.done.Load().(chan?struct{})
?if?pdone?!=?done?{
??return?nil,?false
?}
?return?p,?true
}

timerCtx

簡介

timerCtx 嵌入了 cancelCtx,并新增了一個 timer 和 deadline 字段。timerCtx 的取消能力是復用 cancelCtx 的,只是在這個基礎上增加了定時取消而已。

在我們的使用過程中,有可能還沒到 deadline,任務就提前完成了,此時需要手動調用 CancelFunc。

func?slowOperationWithTimeout(ctx?context.Context)?(Result,?error)?{
??ctx,?cancel?:=?context.WithTimeout(ctx,?100*time.Millisecond)
????defer?cancel()??//?如果未到截止時間,slowOperation就完成了,盡早調用?cancel()?釋放資源
??return?slowOperation(ctx)
}

類型定義

type?timerCtx?struct?{
???cancelCtx?//?內嵌?cancelCtx
???timer?*time.Timer?//?受?cancelCtx.mu?互斥鎖的保護

???deadline?time.Time?//?截止時間
}

Deadline() 返回 deadline 字段的值

func?(c?*timerCtx)?Deadline()?(deadline?time.Time,?ok?bool)?{
?return?c.deadline,?true
}

WithDeadline

WithDeadline 基于parent Context 和 時間點 d,返回了一個定時取消的 Context,以及一個 CancelFunc。返回的Context 有三種情況被取消:1. 到達了指定時間,就會主動取消;2. 手動調用了 CancelFunc;3. 父Context取消,導致該Context被取消。這三種情況哪種先到,就會首次觸發(fā)取消操作,后續(xù)的再次取消不會產生任何效果。

如果傳入 parent Context 的 deadline 比指定的時間 d 還要早,此時 d 就沒用處了,直接依賴 parent 取消傳播就可以了。

func?WithDeadline(parent?Context,?d?time.Time)?(Context,?CancelFunc)?{
??
??//?傳入的?parent?不能為?nil
?if?parent?==?nil?{
??panic("cannot?create?context?from?nil?parent")
?}
??
??//?parent?也有?deadline,并且比?d?還要早,直接依賴?parent?的取消傳播即可
?if?cur,?ok?:=?parent.Deadline();?ok?&&?cur.Before(d)?{
??//?The?current?deadline?is?already?sooner?than?the?new?one.
??return?WithCancel(parent)
?}
??
??//?定義?timerCtx?接口
?c?:=?&timerCtx{
??cancelCtx:?newCancelCtx(parent),
??deadline:??d,
?}
??
??//?設置傳播,如果parent?屬于?cancelCtx,會掛載到?children?字段上
?propagateCancel(parent,?c)
??
??//?距離截止時間?d?還有多久
?dur?:=?time.Until(d)
?if?dur?<=?0?{
????//?已經(jīng)到了截止時間,直接取消,同時從?parent?中取消掛載
????//?由于是超時,取消時的?err?是?DeadlineExceeded
??c.cancel(true,?DeadlineExceeded)?
????
????//?再返回?c?和?CancelFunc,已經(jīng)取消掛載了,此時的?CancelFunc?不會從?parent?中取消掛載
????//?后面再次調用?CancelFunc?不會產生任何效果了
????//?主動取消的話,err?是?Canceled
??return?c,?func()?{?c.cancel(false,?Canceled)?}
?}
??
??//?還沒有到截止時間,定義一個定時器,過了?dur?會自動取消
?c.mu.Lock()
?defer?c.mu.Unlock()
?if?c.err?==?nil?{
??c.timer?=?time.AfterFunc(dur,?func()?{
??????//?由于是到了截止時間才取消,err?是?DeadlineExceeded
???c.cancel(true,?DeadlineExceeded)
??})
?}
??
??//?返回?c?和?cancelFunc,主動取消的?err?是?Canceled
?return?c,?func()?{?c.cancel(true,?Canceled)?}
}

接下來我們看下 cancel 方法,timerCtx 的 cancel 方法 就是調用內嵌 cancelCtx 的 cancel() 方法,默認是不從父節(jié)點移除

func?(c?*timerCtx)?cancel(removeFromParent?bool,?err?error)?{
?c.cancelCtx.cancel(false,?err)
??
??//?從父節(jié)點中移除
?if?removeFromParent?{
??removeChild(c.cancelCtx.Context,?c)
?}
??
??//?把定時器停了,釋放資源
??//?有可能還沒到deadline,手動觸發(fā)了?CancelFunc,此時把?timer?停了
?c.mu.Lock()
?if?c.timer?!=?nil?{
??c.timer.Stop()
??c.timer?=?nil
?}
?c.mu.Unlock()
}

WithTimeout

WithTimeout 就是基于 WithDeadline,deadline 就是基于當前時間計算的

func?WithTimeout(parent?Context,?timeout?time.Duration)?(Context,?CancelFunc)?{
?return?WithDeadline(parent,?time.Now().Add(timeout))
}

總結

本篇文章,我們通過源碼+示例的方式,一起學習了 context 包相關的結構以及實現(xiàn)邏輯,包括如下內容

Context 接口:定義了一些接口方法和規(guī)范

emptyCtx:空的Context,Background() 和 TODO() 方法就是使用的 emptyCtx

valueCtx:用于保存鍵值對,查詢時是遞歸查詢,可以用于 LogID 這種全局 id 的保存

cancelCtx:可以取消的Context,用于取消信號的傳遞

timerCtx:定時取消的 cancelCtx

以上就是Go語言學習之context包的用法詳解的詳細內容,更多關于Go語言 context包的資料請關注腳本之家其它相關文章!

相關文章

  • 6行代碼快速解決golang TCP粘包問題

    6行代碼快速解決golang TCP粘包問題

    在用golang開發(fā)人工客服系統(tǒng)的時候碰到了粘包問題,那么什么是粘包呢?下面這篇文章主要給大家介紹了關于如何通過6行代碼快速解決golang TCP粘包問題的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒下面隨著小編來一起學習學習吧。
    2018-03-03
  • 詳解Go語言如何使用標準庫sort對切片進行排序

    詳解Go語言如何使用標準庫sort對切片進行排序

    Sort?標準庫提供了對基本數(shù)據(jù)類型的切片和自定義類型的切片進行排序的函數(shù)。今天主要分享的內容是使用?Go?標準庫?sort?對切片進行排序,感興趣的可以了解一下
    2022-12-12
  • 源碼剖析Golang如何fork一個進程

    源碼剖析Golang如何fork一個進程

    創(chuàng)建一個新進程分為兩個步驟,一個是fork系統(tǒng)調用,一個是execve?系統(tǒng)調用,本文將從源碼的角度帶大家剖析一下Golang是如何fork一個進程的
    2023-06-06
  • GoLang分布式鎖與snowflake雪花算法

    GoLang分布式鎖與snowflake雪花算法

    這篇文章主要介紹了GoLang分布式鎖與snowflake雪花算法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2022-12-12
  • go開發(fā)alertmanger實現(xiàn)釘釘報警

    go開發(fā)alertmanger實現(xiàn)釘釘報警

    本文主要介紹了go開發(fā)alertmanger實現(xiàn)釘釘報警,通過自己的url實現(xiàn)alertmanager的釘釘報警,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 利用Go語言實現(xiàn)流量回放工具的示例代碼

    利用Go語言實現(xiàn)流量回放工具的示例代碼

    今天給大家推薦一款使用Go語言編寫的流量回放工具?--?goreplay;工作中你一定遇到過需要在服務器上抓包的場景,有了這個工具就可以助你一臂之力,廢話不多,我們接下來來看一看這個工具
    2022-09-09
  • Go語言利用接口實現(xiàn)鏈表插入功能詳解

    Go語言利用接口實現(xiàn)鏈表插入功能詳解

    這篇文章主要為大家介紹了Go語言中的接口,以及如何利用接口實現(xiàn)鏈表插入功能,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2022-04-04
  • Golang 日期/時間包的使用詳解

    Golang 日期/時間包的使用詳解

    這篇文章主要介紹了Golang 日期/時間包的使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • Golang根據(jù)job數(shù)量動態(tài)控制每秒?yún)f(xié)程的最大創(chuàng)建數(shù)量方法詳解

    Golang根據(jù)job數(shù)量動態(tài)控制每秒?yún)f(xié)程的最大創(chuàng)建數(shù)量方法詳解

    這篇文章主要介紹了Golang根據(jù)job數(shù)量動態(tài)控制每秒?yún)f(xié)程的最大創(chuàng)建數(shù)量方法
    2024-01-01
  • Go語言基礎語法和基本數(shù)據(jù)類型知識鞏固

    Go語言基礎語法和基本數(shù)據(jù)類型知識鞏固

    這篇文章主要為大家介紹了Go語言基礎語法和基本數(shù)據(jù)類型知識鞏固,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11

最新評論

怀集县| 聂荣县| 黄陵县| 和静县| 攀枝花市| 长武县| 星子县| 崇信县| 西充县| 鄂尔多斯市| 稷山县| 邓州市| 郧西县| 剑阁县| 且末县| 宁河县| 威海市| 绥江县| 临漳县| 阿鲁科尔沁旗| 佛坪县| 清河县| 静海县| 广河县| 康保县| 循化| 龙井市| 晴隆县| 武宣县| 萍乡市| 舒城县| 湖州市| 铜鼓县| 姜堰市| 克拉玛依市| 金门县| 康平县| 永胜县| 穆棱市| 错那县| 鄱阳县|