詳解Golang并發(fā)控制的三種方案
Channel
Channel是Go在語言層面提供的一種協(xié)程間的通信方式,我們可以通過在協(xié)程中向管道寫入數(shù)據(jù)和在待等待的協(xié)程中讀取對應協(xié)程的次數(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("主進程結束") }
WaitGroup
waitgroup通常應用于等待一組“工作協(xié)程”結束的場景,waitgroup底層是由一個長度為3的數(shù)組實現(xiàn)的,其內(nèi)部有兩個計數(shù)器,一個是工作協(xié)程計數(shù)器、一個是坐等協(xié)程計數(shù)器,還有一個是信號量。工作協(xié)程全部運行結束后,工作協(xié)程計數(shù)器將置為0,會釋放對應坐等協(xié)程次數(shù)的信號量。
兩點注意:
Add()方法中的參數(shù)大小要于工作協(xié)程的數(shù)量相等,否則會導致坐等協(xié)程一直等待,觸發(fā)死鎖panic
Done()方法執(zhí)行的次數(shù)要與Add()方法中的工作協(xié)程計數(shù)器的數(shù)量一致,否則當工作協(xié)程計數(shù)器<0時,會觸發(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é)程結束了") wg.Done() }() go func() { time.Sleep(3 * time.Second) fmt.Println("等待三分鐘的協(xié)程結束了") wg.Done() }() wg.Wait() }
Context
適用于一個協(xié)程派生出多個協(xié)程的情況,可以控制多級的goroutine。我們可以通過一個Context對象,對派生出來的樹狀goroutine進行統(tǒng)一管理,并且每個goroutine具有相同的上下文。做統(tǒng)一關閉操作、統(tǒng)一定時關閉、統(tǒng)一傳值的操作。多個上下文協(xié)程之間可以互相嵌套配合。
golang實現(xiàn)了四種原生的上下文對象
emptyCtx: 該上下文對象一般是作為父節(jié)點的,如果沒有父節(jié)點,我們通常使用context.Background()方法來獲取emptyCtx對象,并將其作為創(chuàng)建其他節(jié)點的父節(jié)點。
cancelCtx: 該上下文對象可以關閉所有擁有同一個上下文的goroutine,通過在子協(xié)程中監(jiān)聽cancelCtx.Done方法,來結束所有的派生協(xié)程。具體代碼看下方,我們通過WithCancel()方法來獲取該對象。
timerCtx:該上下文對象是對cancelCtx對象的進一步封裝,比cancelCtx主動關閉之外,多了了一個定時關閉功能。我們可以通過WithTimeout()和WithDeadline()這兩種方法來獲取該對象。其中WithTimeout()和WithDeadline()這兩種方法點是WithTimeout()是設置過一段時間關閉上下文,WithDeadline()是設置那一個時間點來關閉這一個上下文。
valueCtx:該上下文對象并不用于進行協(xié)程的控制,而是在多級協(xié)程之間進行值得傳遞,方便共享一些相同得上下文內(nèi)容。
以上除emptyCtx外的上下文對象和獲取實例的方法如下圖所示:
Context示例代碼
cancelCtx
我們在所有的派生協(xié)程中傳入相同的cancelContext對象,并在每一個子協(xié)程中使用switch-case結構監(jiān)聽上下文對象是否關閉,如果上下文對象關閉了,ctx.Done()返回的管道就可以讀取到一個元素,使所在的case語句可執(zhí)行,之后退出switch結構,執(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 阻塞當前一層的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舉例,相比與我們之前的手動調(diào)用關閉,使用timerCtx定時上下文對象后,可以是實現(xià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
我們可以通過嵌套WithValue上下文,來進行多個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")) }
到此這篇關于詳解Golang并發(fā)控制的三種方案的文章就介紹到這了,更多相關Golang并發(fā)控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
golang如何實現(xiàn)mapreduce單進程版本詳解
這篇文章主要給大家介紹了關于golang如何實現(xiàn)mapreduce單進程版本的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-01-01Golang try catch與錯誤處理的實現(xiàn)
社區(qū)不少人在談論 golang 為毛不用try/catch模式,而采用苛刻的recovery、panic、defer組合,本文就來詳細的介紹一下,感興趣的可以了解一下2021-07-07詳解golang中的結構體編解碼神器Mapstructure庫
mapstructure是GO字典(map[string]interface{})和Go結構體之間轉(zhuǎn)換的編解碼工具,這篇文章主要為大家介紹一下Mapstructure庫的相關使用,希望對大家有所幫助2023-09-09Golang中的archive/zip包的常用函數(shù)詳解
Golang 中的 archive/zip 包用于處理 ZIP 格式的壓縮文件,提供了一系列用于創(chuàng)建、讀取和解壓縮 ZIP 格式文件的函數(shù)和類型,下面小編就來和大家講解下常用函數(shù)吧2023-08-08