一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Go語言實(shí)戰(zhàn)筆記(二十)| Go Context | 飛雪無情的博客

 鳳凰苑兇真 2018-03-28

《Go語言實(shí)戰(zhàn)》讀書筆記,未完待續(xù),歡迎掃碼關(guān)注公眾號(hào)flysnow_org或者網(wǎng)站http://www./,第一時(shí)間看后續(xù)筆記。覺得有幫助的話,順手分享到朋友圈吧,感謝支持。

控制并發(fā)有兩種經(jīng)典的方式,一種是WaitGroup,另外一種就是Context,今天我就談?wù)凜ontext。

什么是WaitGroup

WaitGroup以前我們?cè)诓l(fā)的時(shí)候介紹過,它是一種控制并發(fā)的方式,它的這種方式是控制多個(gè)goroutine同時(shí)完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
time.Sleep(2*time.Second)
fmt.Println("1號(hào)完成")
wg.Done()
}()
go func() {
time.Sleep(2*time.Second)
fmt.Println("2號(hào)完成")
wg.Done()
}()
wg.Wait()
fmt.Println("好了,大家都干完了,放工")
}

一個(gè)很簡單的例子,一定要例子中的2個(gè)goroutine同時(shí)做完,才算是完成,先做好的就要等著其他未完成的,所有的goroutine要都全部完成才可以。

這是一種控制并發(fā)的方式,這種尤其適用于,好多個(gè)goroutine協(xié)同做一件事情的時(shí)候,因?yàn)槊總€(gè)goroutine做的都是這件事情的一部分,只有全部的goroutine都完成,這件事情才算是完成,這是等待的方式。

在實(shí)際的業(yè)務(wù)種,我們可能會(huì)有這么一種場景:需要我們主動(dòng)的通知某一個(gè)goroutine結(jié)束。比如我們開啟一個(gè)后臺(tái)goroutine一直做事情,比如監(jiān)控,現(xiàn)在不需要了,就需要通知這個(gè)監(jiān)控goroutine結(jié)束,不然它會(huì)一直跑,就泄漏了。

chan通知

我們都知道一個(gè)goroutine啟動(dòng)后,我們是無法控制他的,大部分情況是等待它自己結(jié)束,那么如果這個(gè)goroutine是一個(gè)不會(huì)自己結(jié)束的后臺(tái)goroutine呢?比如監(jiān)控等,會(huì)一直運(yùn)行的。

這種情況化,一直傻瓜式的辦法是全局變量,其他地方通過修改這個(gè)變量完成結(jié)束通知,然后后臺(tái)goroutine不停的檢查這個(gè)變量,如果發(fā)現(xiàn)被通知關(guān)閉了,就自我結(jié)束。

這種方式也可以,但是首先我們要保證這個(gè)變量在多線程下的安全,基于此,有一種更好的方式:chan + select 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
stop := make(chan bool)
go func() {
for {
select {
case <-stop:
fmt.Println("監(jiān)控退出,停止了...")
return
default:
fmt.Println("goroutine監(jiān)控中...")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知監(jiān)控停止")
stop<- true
//為了檢測監(jiān)控過是否停止,如果沒有監(jiān)控輸出,就表示停止了
time.Sleep(5 * time.Second)
}

例子中我們定義一個(gè)stop的chan,通知他結(jié)束后臺(tái)goroutine。實(shí)現(xiàn)也非常簡單,在后臺(tái)goroutine中,使用select判斷stop是否可以接收到值,如果可以接收到,就表示可以退出停止了;如果沒有接收到,就會(huì)執(zhí)行default里的監(jiān)控邏輯,繼續(xù)監(jiān)控,只到收到stop的通知。

有了以上的邏輯,我們就可以在其他goroutine種,給stop chan發(fā)送值了,例子中是在main goroutine中發(fā)送的,控制讓這個(gè)監(jiān)控的goroutine結(jié)束。

發(fā)送了stop<- true結(jié)束的指令后,我這里使用time.Sleep(5 * time.Second)故意停頓5秒來檢測我們結(jié)束監(jiān)控goroutine是否成功。如果成功的話,不會(huì)再有goroutine監(jiān)控中...的輸出了;如果沒有成功,監(jiān)控goroutine就會(huì)繼續(xù)打印goroutine監(jiān)控中...輸出。

這種chan+select的方式,是比較優(yōu)雅的結(jié)束一個(gè)goroutine的方式,不過這種方式也有局限性,如果有很多goroutine都需要控制結(jié)束怎么辦呢?如果這些goroutine又衍生了其他更多的goroutine怎么辦呢?如果一層層的無窮盡的goroutine呢?這就非常復(fù)雜了,即使我們定義很多chan也很難解決這個(gè)問題,因?yàn)間oroutine的關(guān)系鏈就導(dǎo)致了這種場景非常復(fù)雜。

初識(shí)Context

上面說的這種場景是存在的,比如一個(gè)網(wǎng)絡(luò)請(qǐng)求Request,每個(gè)Request都需要開啟一個(gè)goroutine做一些事情,這些goroutine又可能會(huì)開啟其他的goroutine。所以我們需要一種可以跟蹤goroutine的方案,才可以達(dá)到控制他們的目的,這就是Go語言為我們提供的Context,稱之為上下文非常貼切,它就是goroutine的上下文。

下面我們就使用Go Context重寫上面的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("監(jiān)控退出,停止了...")
return
default:
fmt.Println("goroutine監(jiān)控中...")
time.Sleep(2 * time.Second)
}
}
}(ctx)
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知監(jiān)控停止")
cancel()
//為了檢測監(jiān)控過是否停止,如果沒有監(jiān)控輸出,就表示停止了
time.Sleep(5 * time.Second)
}

重寫比較簡單,就是把原來的chan stop 換成Context,使用Context跟蹤goroutine,以便進(jìn)行控制,比如結(jié)束等。

context.Background() 返回一個(gè)空的Context,這個(gè)空的Context一般用于整個(gè)Context樹的根節(jié)點(diǎn)。然后我們使用context.WithCancel(parent)函數(shù),創(chuàng)建一個(gè)可取消的子Context,然后當(dāng)作參數(shù)傳給goroutine使用,這樣就可以使用這個(gè)子Context跟蹤這個(gè)goroutine。

在goroutine中,使用select調(diào)用<-ctx.Done()判斷是否要結(jié)束,如果接受到值的話,就可以返回結(jié)束goroutine了;如果接收不到,就會(huì)繼續(xù)進(jìn)行監(jiān)控。

那么是如何發(fā)送結(jié)束指令的呢?這就是示例中的cancel函數(shù)啦,它是我們調(diào)用context.WithCancel(parent)函數(shù)生成子Context的時(shí)候返回的,第二個(gè)返回值就是這個(gè)取消函數(shù),它是CancelFunc類型的。我們調(diào)用它就可以發(fā)出取消指令,然后我們的監(jiān)控goroutine就會(huì)收到信號(hào),就會(huì)返回結(jié)束。

Context控制多個(gè)goroutine

使用Context控制一個(gè)goroutine的例子如上,非常簡單,下面我們看看控制多個(gè)goroutine的例子,其實(shí)也比較簡單。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx,"【監(jiān)控1】")
go watch(ctx,"【監(jiān)控2】")
go watch(ctx,"【監(jiān)控3】")
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知監(jiān)控停止")
cancel()
//為了檢測監(jiān)控過是否停止,如果沒有監(jiān)控輸出,就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name,"監(jiān)控退出,停止了...")
return
default:
fmt.Println(name,"goroutine監(jiān)控中...")
time.Sleep(2 * time.Second)
}
}
}

示例中啟動(dòng)了3個(gè)監(jiān)控goroutine進(jìn)行不斷的監(jiān)控,每一個(gè)都使用了Context進(jìn)行跟蹤,當(dāng)我們使用cancel函數(shù)通知取消時(shí),這3個(gè)goroutine都會(huì)被結(jié)束。這就是Context的控制能力,它就像一個(gè)控制器一樣,按下開關(guān)后,所有基于這個(gè)Context或者衍生的子Context都會(huì)收到通知,這時(shí)就可以進(jìn)行清理操作了,最終釋放goroutine,這就優(yōu)雅的解決了goroutine啟動(dòng)后不可控的問題。

《Go語言實(shí)戰(zhàn)》讀書筆記,未完待續(xù),歡迎掃碼關(guān)注公眾號(hào)flysnow_org或者網(wǎng)站http://www./,第一時(shí)間看后續(xù)筆記。覺得有幫助的話,順手分享到朋友圈吧,感謝支持。

Context接口

Context的接口定義的比較簡潔,我們看下這個(gè)接口的方法。

1
2
3
4
5
6
7
8
9
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}

這個(gè)接口共有4個(gè)方法,了解這些方法的意思非常重要,這樣我們才可以更好的使用他們。

Deadline方法是獲取設(shè)置的截止時(shí)間的意思,第一個(gè)返回式是截止時(shí)間,到了這個(gè)時(shí)間點(diǎn),Context會(huì)自動(dòng)發(fā)起取消請(qǐng)求;第二個(gè)返回值ok==false時(shí)表示沒有設(shè)置截止時(shí)間,如果需要取消的話,需要調(diào)用取消函數(shù)進(jìn)行取消。

Done方法返回一個(gè)只讀的chan,類型為struct{},我們?cè)趃oroutine中,如果該方法返回的chan可以讀取,則意味著parent context已經(jīng)發(fā)起了取消請(qǐng)求,我們通過Done方法收到這個(gè)信號(hào)后,就應(yīng)該做清理操作,然后退出goroutine,釋放資源。

Err方法返回取消的錯(cuò)誤原因,因?yàn)槭裁碈ontext被取消。

Value方法獲取該Context上綁定的值,是一個(gè)鍵值對(duì),所以要通過一個(gè)Key才可以獲取對(duì)應(yīng)的值,這個(gè)值一般是線程安全的。

以上四個(gè)方法中常用的就是Done了,如果Context取消的時(shí)候,我們就可以得到一個(gè)關(guān)閉的chan,關(guān)閉的chan是可以讀取的,所以只要可以讀取的時(shí)候,就意味著收到Context取消的信號(hào)了,以下是這個(gè)方法的經(jīng)典用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
func Stream(ctx context.Context, out chan<- Value) error {
for {
v, err := DoSomething(ctx)
if err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
}

Context接口并不需要我們實(shí)現(xiàn),Go內(nèi)置已經(jīng)幫我們實(shí)現(xiàn)了2個(gè),我們代碼中最開始都是以這兩個(gè)內(nèi)置的作為最頂層的partent context,衍生出更多的子Context。

1
2
3
4
5
6
7
8
9
10
11
12
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}

一個(gè)是Background,主要用于main函數(shù)、初始化以及測試代碼中,作為Context這個(gè)樹結(jié)構(gòu)的最頂層的Context,也就是根Context。

一個(gè)是TODO,它目前還不知道具體的使用場景,如果我們不知道該使用什么Context的時(shí)候,可以使用這個(gè)。

他們兩個(gè)本質(zhì)上都是emptyCtx結(jié)構(gòu)體類型,是一個(gè)不可取消,沒有設(shè)置截止時(shí)間,沒有攜帶任何值的Context。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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實(shí)現(xiàn)Context接口的方法,可以看到,這些方法什么都沒做,返回的都是nil或者零值。

Context的繼承衍生

有了如上的根Context,那么是如何衍生更多的子Context的呢?這就要靠context包為我們提供的With系列的函數(shù)了。

1
2
3
4
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

這四個(gè)With函數(shù),接收的都有一個(gè)partent參數(shù),就是父Context,我們要基于這個(gè)父Context創(chuàng)建出子Context的意思,這種方式可以理解為子Context對(duì)父Context的繼承,也可以理解為基于父Context的衍生。

通過這些函數(shù),就創(chuàng)建了一顆Context樹,樹的每個(gè)節(jié)點(diǎn)都可以有任意多個(gè)子節(jié)點(diǎn),節(jié)點(diǎn)層級(jí)可以有任意多個(gè)。

WithCancel函數(shù),傳遞一個(gè)父Context作為參數(shù),返回子Context,以及一個(gè)取消函數(shù)用來取消Context。
WithDeadline函數(shù),和WithCancel差不多,它會(huì)多傳遞一個(gè)截止時(shí)間參數(shù),意味著到了這個(gè)時(shí)間點(diǎn),會(huì)自動(dòng)取消Context,當(dāng)然我們也可以不等到這個(gè)時(shí)候,可以提前通過取消函數(shù)進(jìn)行取消。

WithTimeoutWithDeadline基本上一樣,這個(gè)表示是超時(shí)自動(dòng)取消,是多少時(shí)間后自動(dòng)取消Context的意思。

WithValue函數(shù)和取消Context無關(guān),它是為了生成一個(gè)綁定了一個(gè)鍵值對(duì)數(shù)據(jù)的Context,這個(gè)綁定的數(shù)據(jù)可以通過Context.Value方法訪問到,后面我們會(huì)專門講。

大家可能留意到,前三個(gè)函數(shù)都返回一個(gè)取消函數(shù)CancelFunc,這是一個(gè)函數(shù)類型,它的定義非常簡單。

1
type CancelFunc func()

這就是取消函數(shù)的類型,該函數(shù)可以取消一個(gè)Context,以及這個(gè)節(jié)點(diǎn)Context下所有的所有的Context,不管有多少層級(jí)。

WithValue傳遞元數(shù)據(jù)

通過Context我們也可以傳遞一些必須的元數(shù)據(jù),這些數(shù)據(jù)會(huì)附加在Context上以供使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var key string="name"
func main() {
ctx, cancel := context.WithCancel(context.Background())
//附加值
valueCtx:=context.WithValue(ctx,key,"【監(jiān)控1】")
go watch(valueCtx)
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知監(jiān)控停止")
cancel()
//為了檢測監(jiān)控過是否停止,如果沒有監(jiān)控輸出,就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
//取出值
fmt.Println(ctx.Value(key),"監(jiān)控退出,停止了...")
return
default:
//取出值
fmt.Println(ctx.Value(key),"goroutine監(jiān)控中...")
time.Sleep(2 * time.Second)
}
}
}

在前面的例子,我們通過傳遞參數(shù)的方式,把name的值傳遞給監(jiān)控函數(shù)。在這個(gè)例子里,我們實(shí)現(xiàn)一樣的效果,但是通過的是Context的Value的方式。

我們可以使用context.WithValue方法附加一對(duì)K-V的鍵值對(duì),這里Key必須是等價(jià)性的,也就是具有可比性;Value值要是線程安全的。

這樣我們就生成了一個(gè)新的Context,這個(gè)新的Context帶有這個(gè)鍵值對(duì),在使用的時(shí)候,可以通過Value方法讀取ctx.Value(key)。

記住,使用WithValue傳值,一般是必須的值,不要什么值都傳遞。

Context 使用原則

  1. 不要把Context放在結(jié)構(gòu)體中,要以參數(shù)的方式傳遞
  2. 以Context作為參數(shù)的函數(shù)方法,應(yīng)該把Context作為第一個(gè)參數(shù),放在第一位。
  3. 給一個(gè)函數(shù)方法傳遞Context的時(shí)候,不要傳遞nil,如果不知道傳遞什么,就使用context.TODO
  4. Context的Value相關(guān)方法應(yīng)該傳遞必須的數(shù)據(jù),不要什么數(shù)據(jù)都使用這個(gè)傳遞
  5. Context是縣城安全的,可以放心的在多個(gè)goroutine中傳遞

《Go語言實(shí)戰(zhàn)》讀書筆記,未完待續(xù),歡迎掃碼關(guān)注公眾號(hào)flysnow_org或者網(wǎng)站http://www./,第一時(shí)間看后續(xù)筆記。覺得有幫助的話,順手分享到朋友圈吧,感謝支持。

掃碼關(guān)注

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    视频在线免费观看你懂的| 欧美国产日产综合精品| 日本福利写真在线观看| 国产精品十八禁亚洲黄污免费观看| 亚洲中文字幕一区三区| 国产人妻精品区一区二区三区| 91人妻人人澡人人人人精品| 国产av天堂一区二区三区粉嫩| 欧美一区二区三区在线播放| 亚洲一二三四区免费视频| 太香蕉久久国产精品视频| 欧美成人高清在线播放| 色婷婷亚洲精品综合网| 欧美国产精品区一区二区三区| 欧美日韩一级aa大片| 日韩精品免费一区二区三区| 日本少妇中文字幕不卡视频| 欧美精品女同一区二区| 91精品国产综合久久福利| 日本一本不卡免费视频 | 欧美精品女同一区二区| 国产精品白丝久久av| 午夜福利激情性生活免费视频| 欧美夫妻性生活一区二区| 福利视频一区二区三区| 丝袜破了有美女肉体免费观看| 精品香蕉国产一区二区三区| 精品国产一区二区欧美| 亚洲精品深夜福利视频| 人妻内射精品一区二区| 日韩精品成区中文字幕| 一二区不卡不卡在线观看 | 国产又大又猛又粗又长又爽| 国产成人亚洲综合色就色| 国产精品偷拍视频一区| 东京热加勒比一区二区三区| 精品国自产拍天天青青草原| 中文字幕亚洲精品乱码加勒比| 亚洲男人天堂网在线视频| 九九热这里只有精品哦| 国产欧美日本在线播放|