詳解Golang并發(fā)控制的三種方案
Channel
Channel是Go在語(yǔ)言層面提供的一種協(xié)程間的通信方式,我們可以通過(guò)在協(xié)程中向管道寫(xiě)入數(shù)據(jù)和在待等待的協(xié)程中讀取對(duì)應(yīng)協(xié)程的次數(shù)來(lái)實(shí)現(xiàn)并發(fā)控制。
func main() { intChan := make(chan int, 5) waitCount := 5 for i := 0; i < waitCount; i++ { go func() { intChan <- 1 }() } for i := 0; i < waitCount; i++ { <-intChan } fmt.Println("主進(jìn)程結(jié)束") }
WaitGroup
waitgroup通常應(yīng)用于等待一組“工作協(xié)程”結(jié)束的場(chǎng)景,waitgroup底層是由一個(gè)長(zhǎng)度為3的數(shù)組實(shí)現(xiàn)的,其內(nèi)部有兩個(gè)計(jì)數(shù)器,一個(gè)是工作協(xié)程計(jì)數(shù)器、一個(gè)是坐等協(xié)程計(jì)數(shù)器,還有一個(gè)是信號(hào)量。工作協(xié)程全部運(yùn)行結(jié)束后,工作協(xié)程計(jì)數(shù)器將置為0,會(huì)釋放對(duì)應(yīng)坐等協(xié)程次數(shù)的信號(hào)量。
兩點(diǎn)注意:
Add()方法中的參數(shù)大小要于工作協(xié)程的數(shù)量相等,否則會(huì)導(dǎo)致坐等協(xié)程一直等待,觸發(fā)死鎖panic
Done()方法執(zhí)行的次數(shù)要與Add()方法中的工作協(xié)程計(jì)數(shù)器的數(shù)量一致,否則當(dāng)工作協(xié)程計(jì)數(shù)器<0時(shí),會(huì)觸發(fā)panic【panic: sync: negative WaitGroup counter】
func main() { wg := sync.WaitGroup{} wg.Add(2) go func() { time.Sleep(3 * time.Second) fmt.Println("等待三分鐘的協(xié)程結(jié)束了") wg.Done() }() go func() { time.Sleep(3 * time.Second) fmt.Println("等待三分鐘的協(xié)程結(jié)束了") wg.Done() }() wg.Wait() }
Context
適用于一個(gè)協(xié)程派生出多個(gè)協(xié)程的情況,可以控制多級(jí)的goroutine。我們可以通過(guò)一個(gè)Context對(duì)象,對(duì)派生出來(lái)的樹(shù)狀goroutine進(jìn)行統(tǒng)一管理,并且每個(gè)goroutine具有相同的上下文。做統(tǒng)一關(guān)閉操作、統(tǒng)一定時(shí)關(guān)閉、統(tǒng)一傳值的操作。多個(gè)上下文協(xié)程之間可以互相嵌套配合。
golang實(shí)現(xiàn)了四種原生的上下文對(duì)象
emptyCtx: 該上下文對(duì)象一般是作為父節(jié)點(diǎn)的,如果沒(méi)有父節(jié)點(diǎn),我們通常使用context.Background()方法來(lái)獲取emptyCtx對(duì)象,并將其作為創(chuàng)建其他節(jié)點(diǎn)的父節(jié)點(diǎn)。
cancelCtx: 該上下文對(duì)象可以關(guān)閉所有擁有同一個(gè)上下文的goroutine,通過(guò)在子協(xié)程中監(jiān)聽(tīng)cancelCtx.Done方法,來(lái)結(jié)束所有的派生協(xié)程。具體代碼看下方,我們通過(guò)WithCancel()方法來(lái)獲取該對(duì)象。
timerCtx:該上下文對(duì)象是對(duì)cancelCtx對(duì)象的進(jìn)一步封裝,比cancelCtx主動(dòng)關(guān)閉之外,多了了一個(gè)定時(shí)關(guān)閉功能。我們可以通過(guò)WithTimeout()和WithDeadline()這兩種方法來(lái)獲取該對(duì)象。其中WithTimeout()和WithDeadline()這兩種方法點(diǎn)是WithTimeout()是設(shè)置過(guò)一段時(shí)間關(guān)閉上下文,WithDeadline()是設(shè)置那一個(gè)時(shí)間點(diǎn)來(lái)關(guān)閉這一個(gè)上下文。
valueCtx:該上下文對(duì)象并不用于進(jìn)行協(xié)程的控制,而是在多級(jí)協(xié)程之間進(jìn)行值得傳遞,方便共享一些相同得上下文內(nèi)容。
以上除emptyCtx外的上下文對(duì)象和獲取實(shí)例的方法如下圖所示:
Context示例代碼
cancelCtx
我們?cè)谒械呐缮鷧f(xié)程中傳入相同的cancelContext對(duì)象,并在每一個(gè)子協(xié)程中使用switch-case結(jié)構(gòu)監(jiān)聽(tīng)上下文對(duì)象是否關(guān)閉,如果上下文對(duì)象關(guān)閉了,ctx.Done()返回的管道就可以讀取到一個(gè)元素,使所在的case語(yǔ)句可執(zhí)行,之后退出switch結(jié)構(gòu),執(zhí)行協(xié)程中的其他代碼。
func main() { ctx, cancelFunc := context.WithCancel(context.Background()) deadline, ok := ctx.Deadline() fmt.Println(deadline, ok) done := ctx.Done() fmt.Println(reflect.TypeOf(done)) fmt.Println(done) go HandelRequest(ctx) //<-done 阻塞當(dāng)前一層的goroutine time.Sleep(5 * time.Second) fmt.Println("all goroutines is stopping!") cancelFunc() err := ctx.Err() fmt.Println(err) //context canceled time.Sleep(5 * time.Second) } func HandelRequest(ctx context.Context) { go WriteMysql(ctx) go WriteRedis(ctx) for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done") return default: fmt.Println("等一等,Handler正在執(zhí)行中") time.Sleep(2 * time.Second) } } } func WriteRedis(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteRedis Done.") return default: fmt.Println("等一等,Redis正在執(zhí)行中") time.Sleep(2 * time.Second) } } } func WriteMysql(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteMysql Done.") return default: fmt.Println("等一等,Mysql正在執(zhí)行中") time.Sleep(2 * time.Second) } } }
timerCtx
這里代碼以WithTimeout舉例,相比與我們之前的手動(dòng)調(diào)用關(guān)閉,使用timerCtx定時(shí)上下文對(duì)象后,可以是實(shí)現(xiàn)到達(dá)指定的時(shí)間自動(dòng)進(jìn)行關(guān)閉的操作。
func main() { deadline, _ := context.WithTimeout(context.Background(), 5*time.Second) go HandelRequest(deadline) time.Sleep(10 * time.Second) } func HandelRequest(ctx context.Context) { go WriteMysql(ctx) go WriteRedis(ctx) for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done") return default: fmt.Println("等一等,Handler正在執(zhí)行中") time.Sleep(2 * time.Second) } } } func WriteRedis(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteRedis Done.") return default: fmt.Println("等一等,Redis正在執(zhí)行中") time.Sleep(2 * time.Second) } } } func WriteMysql(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteMysql Done.") return default: fmt.Println("等一等,Mysql正在執(zhí)行中") time.Sleep(2 * time.Second) } } }
valueCtx
我們可以通過(guò)嵌套WithValue上下文,來(lái)進(jìn)行多個(gè)key-value在派生協(xié)程中傳遞,共享常量
func main() { ctx, cancelFunc := context.WithCancel(context.Background()) value1 := context.WithValue(ctx, "param1", 1) value2 := context.WithValue(value1, "param2", 2) go ReadContextValue(value2) time.Sleep(10 * time.Second) cancelFunc() time.Sleep(5 * time.Second) } func ReadContextValue(ctx context.Context) { fmt.Println(ctx.Value("param1")) fmt.Println(ctx.Value("param2")) }
到此這篇關(guān)于詳解Golang并發(fā)控制的三種方案的文章就介紹到這了,更多相關(guān)Golang并發(fā)控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang如何實(shí)現(xiàn)mapreduce單進(jìn)程版本詳解
這篇文章主要給大家介紹了關(guān)于golang如何實(shí)現(xiàn)mapreduce單進(jìn)程版本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01Golang try catch與錯(cuò)誤處理的實(shí)現(xiàn)
社區(qū)不少人在談?wù)?nbsp;golang 為毛不用try/catch模式,而采用苛刻的recovery、panic、defer組合,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2021-07-07詳解golang中的結(jié)構(gòu)體編解碼神器Mapstructure庫(kù)
mapstructure是GO字典(map[string]interface{})和Go結(jié)構(gòu)體之間轉(zhuǎn)換的編解碼工具,這篇文章主要為大家介紹一下Mapstructure庫(kù)的相關(guān)使用,希望對(duì)大家有所幫助2023-09-09Go語(yǔ)言常見(jiàn)錯(cuò)誤之誤用init函數(shù)實(shí)例解析
Go語(yǔ)言中的init函數(shù)為開(kāi)發(fā)者提供了一種在程序正式運(yùn)行前初始化包級(jí)變量的機(jī)制,然而,由于init函數(shù)的特殊性,不當(dāng)?shù)厥褂盟赡芤鹨幌盗袉?wèn)題,本文將深入探討如何有效地使用init函數(shù),列舉常見(jiàn)誤用并提供相應(yīng)的避免策略2024-01-01Golang WaitGroup實(shí)現(xiàn)原理解析
WaitGroup是Golang并發(fā)的兩種方式之一,一個(gè)是Channel,另一個(gè)是WaitGroup,下面這篇文章主要給大家介紹了關(guān)于golang基礎(chǔ)之waitgroup用法以及使用要點(diǎn)的相關(guān)資料,需要的朋友可以參考下2023-02-02Golang中的archive/zip包的常用函數(shù)詳解
Golang 中的 archive/zip 包用于處理 ZIP 格式的壓縮文件,提供了一系列用于創(chuàng)建、讀取和解壓縮 ZIP 格式文件的函數(shù)和類(lèi)型,下面小編就來(lái)和大家講解下常用函數(shù)吧2023-08-08