Go語(yǔ)言學(xué)習(xí)之context包的用法詳解_第1頁(yè)
Go語(yǔ)言學(xué)習(xí)之context包的用法詳解_第2頁(yè)
Go語(yǔ)言學(xué)習(xí)之context包的用法詳解_第3頁(yè)
Go語(yǔ)言學(xué)習(xí)之context包的用法詳解_第4頁(yè)
Go語(yǔ)言學(xué)習(xí)之context包的用法詳解_第5頁(yè)
已閱讀5頁(yè),還剩18頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第Go語(yǔ)言學(xué)習(xí)之context包的用法詳解目錄前言需求一需求二Context接口emptyCtxvalueCtx類型定義WithValuecancelCtx類型定義cancelCtxWithCanceltimerCtx類型定義WithDeadlineWithTimeout總結(jié)

前言

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

需求一

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

//

睡眠5s,模擬長(zhǎng)時(shí)間操作

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

//

調(diào)用FuncB(),將結(jié)果保存至

channel

go

func()

{

res,

err

=

FuncB()

ch

-

res

//

設(shè)置一個(gè)2s的定時(shí)器

timer

:=

time.NewTimer(2

*

time.Second)

//

監(jiān)測(cè)是定時(shí)器先結(jié)束,還是

FuncB

先返回結(jié)果

select

{

//

超時(shí),返回默認(rèn)值

case

-timer.C:

return

"default",

err

//

FuncB

先返回結(jié)果,關(guān)閉定時(shí)器,返回

FuncB

的結(jié)果

case

r

:=

-ch:

if

!timer.Stop()

{

-timer.C

return

r,

err

func

main()

{

res,

err

:=

FuncA()

fmt.Println(res,

err)

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

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

需求二

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

const

LogKey

=

"LogKey"

//

模擬一個(gè)日志打印,每次從

Context

中取出

LogKey

對(duì)應(yīng)的

Value

作為L(zhǎng)ogID

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

//

日志打印

調(diào)用

FuncB

func

FuncA(ctx

context.Context)

{

(ctx,

"FuncA")

FuncB(ctx)

func

FuncB(ctx

context.Context)

{

(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ù)往下看,一起來(lái)學(xué)習(xí)valueCtx是怎么實(shí)現(xiàn)的吧!

Context接口

type

Context

interface

{

Deadline()

(deadline

time.Time,

ok

bool)

Done()

-chan

struct{}

Err()

error

Value(key

interface{})

interface{}

Context接口比較簡(jiǎn)單,定義了四個(gè)方法:

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

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

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ù),說(shuō)明獲取到了任務(wù)取消的信號(hào)

select

{

case

-ctx.Done():

return

ctx.Err()

//

否則將結(jié)果輸出,繼續(xù)計(jì)算

case

out

-

v:

Value()也是一個(gè)比較常用的方法,用于在上下文中傳遞一些數(shù)據(jù)。使用context.WithValue()方法存入key和value,通過(guò)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接口并不需要我們自己去手動(dòng)實(shí)現(xiàn),一般我們都是直接使用context包中提供的Background()方法和TODO()方法,來(lái)獲取最基礎(chǔ)的Context。

var

(

background

=

new(emptyCtx)

todo

=

new(emptyCtx)

func

Background()

Context

{

return

background

func

TODO()

Context

{

return

todo

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

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

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"

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

valueCtx

使用示例

我們一般使用context.WithValue()方法向Context存入鍵值對(duì),然后通過(guò)Value()方法根據(jù)key得到value,此種功能的實(shí)現(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結(jié)構(gòu)體中嵌套了Context,使用key、value來(lái)保存鍵值對(duì):

type

valueCtx

struct

{

Context

key,

val

interface{}

WithValue

context包對(duì)外暴露了WithValue方法,基于一個(gè)parentcontext來(lái)創(chuàng)建一個(gè)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實(shí)現(xiàn)了Value(),可以根據(jù)key得到value。這是一個(gè)向上遞歸尋找的過(guò)程,如果key不在當(dāng)前valueCtx中,會(huì)繼續(xù)向上找parentContext,直到找到最頂層的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是一個(gè)用于取消任務(wù)的Context,任務(wù)通過(guò)監(jiān)聽(tīng)Context是否被取消,來(lái)決定是否繼續(xù)處理任務(wù)還是直接返回。

如下示例中,我們?cè)趍ain函數(shù)定義了一個(gè)cancelCtx,并在2s后調(diào)用cancel()取消Context,即我們希望doSomething()在2s內(nèi)完成任務(wù),否則就可以直接返回,不需要再繼續(xù)計(jì)算浪費(fèi)資源了。

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

這只是一層調(diào)用,真實(shí)情況下可能會(huì)有多級(jí)調(diào)用,比如doSomething可能又會(huì)調(diào)用其他任務(wù),一旦parentContext取消,后續(xù)的所有任務(wù)都應(yīng)該取消。

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

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

canceler接口包含cancel()和Done()方法,*cancelCtx和*timerCtx均實(shí)現(xiàn)了這個(gè)接口。closedchan是一個(gè)被關(guān)閉的channel,可以用于后面Done()返回canceled是一個(gè)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是一個(gè)函數(shù)類型定義,是一個(gè)取消函數(shù),有如下規(guī)范:

CancelFunc告訴一個(gè)任務(wù)停止工作CancelFunc不會(huì)等待任務(wù)結(jié)束CancelFunc支持并發(fā)調(diào)用第一次調(diào)用后,后續(xù)的調(diào)用不會(huì)產(chǎn)生任何效果

type

CancelFunc

func()

cancelCtxKey是一個(gè)固定的key,用來(lái)返回cancelCtx自身

var

cancelCtxKey

int

cancelCtx

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

type

cancelCtx

struct

{

Context

mu

sync.Mutex

//

保護(hù)如下字段,保證線程安全

done

atomic.Value

//

保存

channel,懶加載,調(diào)用

cancel

方法時(shí)會(huì)關(guān)閉這個(gè)

channel

children

map[canceler]struct{}

//

保存子節(jié)點(diǎn),第一次調(diào)用

cancel

方法時(shí)會(huì)置為

nil

err

error

//

保存為什么被取消,默認(rèn)為nil,第一次調(diào)用

cancel

會(huì)賦值

*cancelCtx的Value()方法和*valueCtx的Value()方法類似,只不過(guò)加了個(gè)固定的key:cancelCtxKey。當(dāng)key為cancelCtxKey時(shí)返回自身

func

(c

*cancelCtx)

Value(key

interface{})

interface{}

{

if

key

==

cancelCtxKey

{

return

c

return

c.Context.Value(key)

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

func

(c

*cancelCtx)

Done()

-chan

struct{}

{

d

:=

c.done.Load()

//

如果已經(jīng)有值了,直接返回

if

d

!=

nil

{

return

d.(chan

struct{})

//

沒(méi)有值,加鎖賦值

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

那么我們?nèi)绾涡陆ㄒ粋€(gè)cancelCtx呢?context包提供了WithCancel()方法,讓我們基于一個(gè)Context來(lái)創(chuàng)建一個(gè)cancelCtx。WithCancel()方法返回兩個(gè)字段,一個(gè)是基于傳入的Context生成的cancelCtx,另一個(gè)是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調(diào)用了兩個(gè)外部方法:newCancelCtx、propagateCancel。newCancelCtx比較簡(jiǎn)單,根據(jù)傳入的context,返回了一個(gè)cancelCtx結(jié)構(gòu)體。

func

newCancelCtx(parent

Context)

cancelCtx

{

return

cancelCtx{Context:

parent}

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

在propagateCancel中,如果父Context是cancelCtx類型且未取消,會(huì)將子Context掛在它下面,形成一個(gè)樹結(jié)構(gòu);其余情況都不會(huì)掛載。

func

propagateCancel(parent

Context,

child

canceler)

{

//

如果

parent

不支持取消,那么就不支持取消傳播,直接返回

done

:=

parent.Done()

if

done

==

nil

{

return

//

到這里說(shuō)明

done

不為

nil,parent

支持取消

select

{

case

-done:

//

如果

parent

此時(shí)已經(jīng)取消了,那么直接告訴子節(jié)點(diǎn)也取消

child.cancel(false,

parent.Err())

return

default:

//

到這里說(shuō)明此時(shí)

parent

還未取消

//

如果

parent

是未取消的

cancelCtx

if

p,

ok

:=

parentCancelCtx(parent);

ok

{

//

加鎖,防止并發(fā)更新

p.mu.Lock()

//

再次判斷,因?yàn)橛锌赡苌弦粋€(gè)獲得鎖的進(jìn)行了取消操作。

//

如果

parent

已經(jīng)取消了,那么子節(jié)點(diǎn)也直接取消

if

p.err

!=

nil

{

child.cancel(false,

p.err)

}

else

{

//

把子Context

掛到父節(jié)點(diǎn)

parent

cancelCtx

children字段下

//

之后

parent

cancelCtx

取消時(shí),能通知到所有的

子Context

if

p.children

==

nil

{

p.children

=

make(map[canceler]struct{})

p.children[child]

=

struct{}{}

p.mu.Unlock()

}

else

{

//

parent

不是

cancelCtx

類型,可能是用戶自己實(shí)現(xiàn)的Context

atomic.AddInt32(goroutines,

+1)

//

啟動(dòng)一個(gè)協(xié)程監(jiān)聽(tīng),如果

parent

取消了,子

Context

也取消

go

func()

{

select

{

case

-parent.Done():

child.cancel(false,

parent.Err())

case

-child.Done():

}()

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

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

propagateCancel()中有三處調(diào)用了cancel()方法,傳入的removeFromParent都為false,是因?yàn)楫?dāng)時(shí)根本沒(méi)有掛載,不需要移除。而WithCancel返回的CancelFunc,傳入的removeFromParent為true,是因?yàn)檎{(diào)用propagateCancel有可能產(chǎn)生掛載,當(dāng)產(chǎn)生掛載時(shí),調(diào)用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")

//

涉及到保護(hù)字段值的修改,都需要加鎖

c.mu.Lock()

//

如果該Context已經(jīng)取消過(guò)了,直接返回。多次調(diào)用cancel,不會(huì)產(chǎn)生額外效果

if

c.err

!=

nil

{

c.mu.Unlock()

return

//

err

賦值,這里

err

一定不為

nil

c.err

=

err

//

close

channel

d,

_

:=

c.done.Load().(chan

struct{})

//

因?yàn)閏.done

是懶加載,有可能存在

nil

的情況

//

如果

c.done

中沒(méi)有值,直接賦值

closedchan;否則直接

close

if

d

==

nil

{

c.done.Store(closedchan)

}

else

{

close(d)

//

遍歷當(dāng)前

cancelCtx

所有的子Context,讓子節(jié)點(diǎn)也

cancel

//

因?yàn)楫?dāng)前的Context

會(huì)主動(dòng)把子Context移除,子Context

不用主動(dòng)從parent中脫離

//

因此

child.cancel

傳入的

removeFromParent

為false

for

child

:=

range

c.children

{

child.cancel(false,

err)

//

children

置空,相當(dāng)于移除自己的所有子Context

c.children

=

nil

c.mu.Unlock()

//

如果當(dāng)前

cancelCtx

需要從上層的

cancelCtx移除,調(diào)用removeChild方法

//

c.Context

就是自己的父Context

if

removeFromParent

{

removeChild(c.Context,

c)

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

如果上一步propagateCancel方法將自己掛載到了A上,但是在調(diào)用cancel()時(shí),A已經(jīng)取消過(guò)了,此時(shí)parentCancelCtx()會(huì)返回false。不過(guò)這沒(méi)有關(guān)系,A取消時(shí)已經(jīng)將掛載的子節(jié)點(diǎn)移除了,當(dāng)前的子節(jié)點(diǎn)不用將自己從A中移除了。

func

removeChild(parent

Context,

child

canceler)

{

//

parent

是否為未取消的

cancelCtx

p,

ok

:=

parentCancelCtx(parent)

if

!ok

{

return

//

獲取

parent

cancelCtx

的鎖,修改保護(hù)字段

children

p.mu.Lock()

//

將自己從

parent

cancelCtx

children

中刪除

if

p.children

!=

nil

{

delete(p.children,

child)

p.mu.Unlock()

parentCancelCtx判斷parent是否為未取消的*cancelCtx。取消與否容易判斷,難判斷的是parent是否為*cancelCtx,因?yàn)橛锌赡芷渌Y(jié)構(gòu)體內(nèi)嵌了cancelCtx,比如timerCtx,會(huì)通過(guò)比對(duì)channel來(lái)確定。

func

parentCancelCtx(parent

Context)

(*cancelCtx,

bool)

{

//

如果

parent

context

done

nil,

說(shuō)明不支持

cancel,那么就不可能是

cancelCtx

//

如果

parent

context

done

closedchan,

說(shuō)明

parent

context

已經(jīng)

cancel

done

:=

parent.Done()

if

done

==

closedchan

||

done

==

nil

{

return

nil,

false

//

到這里說(shuō)明支持取消,且沒(méi)有被取消

//

如果

parent

context

屬于原生的

*cancelCtx

或衍生類型,需要繼續(xù)進(jìn)行后續(xù)判斷

//

如果

parent

context

無(wú)法轉(zhuǎn)換到

*cancelCtx,則認(rèn)為非

cancelCtx,返回

nil,fasle

p,

ok

:=

parent.Value(cancelCtxKey).(*cancelCtx)

if

!ok

{

return

nil,

false

//

經(jīng)過(guò)上面的判斷后,說(shuō)明

parent

context

可以被轉(zhuǎn)換為

*cancelCtx,這時(shí)存在多種情況:

//

-

parent

context

就是

*cancelCtx

//

-

parent

context

是標(biāo)準(zhǔn)庫(kù)中的

timerCtx

//

-

parent

context

是個(gè)自己自定義包裝的

cancelCtx

//

針對(duì)這

3

種情況需要進(jìn)行判斷,判斷方法就是:

//

判斷

parent

context

通過(guò)

Done()

方法獲取的

done

channel

Value

查找到的

context

done

channel

是否一致

//

一致情況說(shuō)明

parent

context

cancelCtx

timerCtx

自定義的

cancelCtx

且未重寫

Done(),

//

這種情況下可以認(rèn)為拿到了底層的

*cancelCtx

//

不一致情況說(shuō)明

parent

context

是一個(gè)自定義的

cancelCtx

且重寫了

Done()

方法,并且并未返回標(biāo)準(zhǔn)

*cancelCtx

//

done

channel,這種情況需要單獨(dú)處理,故返回

nil,

false

pdone,

_

:=

p.done.Load().(chan

struct{})

if

pdone

!=

done

{

return

nil,

false

return

p,

true

timerCtx

簡(jiǎn)介

timerCtx嵌入了cancelCtx,并新增了一個(gè)timer和deadline字段。timerCtx的取消能力是復(fù)用cancelCtx的,只是在這個(gè)基礎(chǔ)上增加了定時(shí)取消而已。

在我們的使用過(guò)程中,有可能還沒(méi)到deadline,任務(wù)就提前完成了,此時(shí)需要手動(dòng)調(diào)用CancelFunc。

func

slowOperationWithTimeout(ctx

context.Context)

(Result,

error)

{

ctx,

cancel

:=

context.WithTimeout(ctx,

100*time.Millisecond)

defer

cancel()

//

如果未到截止時(shí)間,slowOperation就完成了,盡早調(diào)用

cancel()

釋放資源

return

slowOperation(ctx)

type

timerCtx

struct

{

cancelCtx

//

內(nèi)嵌

cancelCtx

timer

*time.Timer

//

cancelCtx.mu

互斥鎖的保護(hù)

deadline

time.Time

//

截止時(shí)間

Deadline()返回deadline字段的值

func

(c

*timerCtx)

Deadline()

(deadline

time.Time,

ok

bool)

{

return

c.deadline,

true

WithDeadline

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

如果傳入parentContext的deadline比指定的時(shí)間d還要早,此時(shí)d就沒(méi)用處了,直接依賴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,

//

設(shè)置傳播,如果parent

屬于

cancelC

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論