Golang中常見的三種并發(fā)控制方式使用小結(jié)
Go語(yǔ)言中的goroutine是一種輕量級(jí)的線程,其優(yōu)點(diǎn)在于占用資源少、切換成本低,能夠高效地實(shí)現(xiàn)并發(fā)操作。但如何對(duì)這些并發(fā)的goroutine進(jìn)行控制呢?
一提到并發(fā)控制,大家最先想到到的是鎖。Go中同樣提供了鎖的相關(guān)機(jī)制,包括互斥鎖sync.Mutex
和讀寫鎖sync.RWMutex
;除此之外Go還提供了原子操作sync/atomic
。但這些操作都是針對(duì)并發(fā)過(guò)程中的數(shù)據(jù)安全的,并不是針對(duì)goroutine本身的。
本文主要介紹的是對(duì)goroutine并發(fā)行為的控制。在Go中最常見的有三種方式:sync.WaitGroup、channel和Context。
1. sync.WaitGroup
sync.WaitGroup是Go語(yǔ)言中一個(gè)非常有用的同步原語(yǔ),它可以幫助我們等待一組goroutine全部完成。在以下場(chǎng)景中,我們通常會(huì)使用sync.WaitGroup:
- 當(dāng)我們需要在主函數(shù)中等待一組goroutine全部完成后再退出程序時(shí)。
- 當(dāng)我們需要在一個(gè)函數(shù)中啟動(dòng)多個(gè)goroutine,并確保它們?nèi)客瓿珊笤俜祷亟Y(jié)果時(shí)。
- 當(dāng)我們需要在一個(gè)函數(shù)中啟動(dòng)多個(gè)goroutine,并確保它們?nèi)客瓿珊笤賵?zhí)行某個(gè)操作時(shí)。
- 當(dāng)我們需要在一個(gè)函數(shù)中啟動(dòng)多個(gè)goroutine,并確保它們?nèi)客瓿珊笤訇P(guān)閉某個(gè)資源時(shí)。
- 當(dāng)我們需要在一個(gè)函數(shù)中啟動(dòng)多個(gè)goroutine,并確保它們?nèi)客瓿珊笤偻顺鲅h(huán)時(shí)。
在使用sync.WaitGroup
時(shí),我們需要先創(chuàng)建一個(gè)sync.WaitGroup
對(duì)象,然后使用它的Add
方法來(lái)指定需要等待的goroutine數(shù)量。接著,我們可以使用go關(guān)鍵字來(lái)啟動(dòng)多個(gè)goroutine,并在每個(gè)goroutine中使用sync.WaitGroup
對(duì)象的Done
方法來(lái)表示該goroutine已經(jīng)完成。最后,我們可以使用sync.WaitGroup
對(duì)象的Wait
方法來(lái)等待所有的goroutine全部完成。
下面是一個(gè)簡(jiǎn)單的示例,會(huì)啟動(dòng)3個(gè)goroutine,分別休眠0s、1s和2s,主函數(shù)會(huì)在這3個(gè)goroutine結(jié)束后退出:
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Printf("sub goroutine sleep: %ds\n", i) time.Sleep(time.Duration(i) * time.Second) }(i) } wg.Wait() fmt.Println("main func done") }
2. channel
在Go語(yǔ)言中,使用channel可以幫助我們更好地控制goroutine的并發(fā)。以下是一些常見的使用channel來(lái)控制goroutine并發(fā)的方法:
2.1 使用無(wú)緩沖channel進(jìn)行同步
我們可以使用一個(gè)無(wú)緩沖的channel來(lái)實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式,其中一個(gè)goroutine負(fù)責(zé)生產(chǎn)數(shù)據(jù),另一個(gè)goroutine負(fù)責(zé)消費(fèi)數(shù)據(jù)。當(dāng)生產(chǎn)者goroutine將數(shù)據(jù)發(fā)送到channel時(shí),消費(fèi)者goroutine會(huì)阻塞等待數(shù)據(jù)的到來(lái)。這樣,我們可以確保生產(chǎn)者和消費(fèi)者之間的數(shù)據(jù)同步。
下面是一個(gè)簡(jiǎn)單的示例代碼:
package main import ( "fmt" "sync" "time" ) func producer(ch chan int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 10; i++ { ch <- i fmt.Println("produced", i) time.Sleep(100 * time.Millisecond) } close(ch) } func consumer(ch chan int, wg *sync.WaitGroup) { defer wg.Done() for i := range ch { fmt.Println("consumed", i) time.Sleep(150 * time.Millisecond) } } func main() { var wg sync.WaitGroup ch := make(chan int) wg.Add(2) go producer(ch, &wg) go consumer(ch, &wg) wg.Wait() }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)無(wú)緩沖的channel,用于在生產(chǎn)者goroutine和消費(fèi)者goroutine之間傳遞數(shù)據(jù)。生產(chǎn)者goroutine將數(shù)據(jù)發(fā)送到channel中,消費(fèi)者goroutine從channel中接收數(shù)據(jù)。在生產(chǎn)者goroutine中,我們使用time.Sleep函數(shù)來(lái)模擬生產(chǎn)數(shù)據(jù)的時(shí)間,在消費(fèi)者goroutine中,我們使用time.Sleep函數(shù)來(lái)模擬消費(fèi)數(shù)據(jù)的時(shí)間。最后,我們使用sync.WaitGroup來(lái)等待所有的goroutine全部完成。
2.2 使用有緩沖channel進(jìn)行限流
我們可以使用一個(gè)有緩沖的channel來(lái)限制并發(fā)goroutine的數(shù)量。在這種情況下,我們可以將channel的容量設(shè)置為我們希望的最大并發(fā)goroutine數(shù)量。然后,在啟動(dòng)每個(gè)goroutine之前,我們將一個(gè)值發(fā)送到channel中。在goroutine完成后,我們從channel中接收一個(gè)值。這樣,我們可以保證同時(shí)運(yùn)行的goroutine數(shù)量不超過(guò)我們指定的最大并發(fā)數(shù)量。
下面是一個(gè)簡(jiǎn)單的示例代碼:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup maxConcurrency := 3 semaphore := make(chan struct{}, maxConcurrency) for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() semaphore <- struct{}{} fmt.Println("goroutine", i, "started") // do some work fmt.Println("goroutine", i, "finished") <-semaphore }() } wg.Wait() }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)帶緩沖的channel,緩沖區(qū)大小為3。然后,我們啟動(dòng)了10個(gè)goroutine,在每個(gè)goroutine中,我們將一個(gè)空結(jié)構(gòu)體發(fā)送到channel中,表示該goroutine已經(jīng)開始執(zhí)行。在goroutine完成后,我們從channel中接收一個(gè)空結(jié)構(gòu)體,表示該goroutine已經(jīng)完成執(zhí)行。這樣,我們可以保證同時(shí)運(yùn)行的goroutine數(shù)量不超過(guò)3。
3. Context
在Go語(yǔ)言中,使用Context可以幫助我們更好地控制goroutine的并發(fā)。以下是一些常見的使用Context來(lái)控制goroutine并發(fā)的方法:
3.1 超時(shí)控制
在某些情況下,我們需要對(duì)goroutine的執(zhí)行時(shí)間進(jìn)行限制,以避免程序長(zhǎng)時(shí)間阻塞或者出現(xiàn)死鎖等問(wèn)題。使用Context可以幫助我們更好地控制goroutine的執(zhí)行時(shí)間。我們可以創(chuàng)建一個(gè)帶有超時(shí)時(shí)間的Context,然后將其傳遞給goroutine。如果goroutine在超時(shí)時(shí)間內(nèi)沒有完成執(zhí)行,我們可以使用Context的Done方法來(lái)取消goroutine的執(zhí)行。
下面是一個(gè)簡(jiǎn)單的示例代碼:
package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() go func() { for { select { case <-ctx.Done(): fmt.Println("goroutine finished") return default: fmt.Println("goroutine running") time.Sleep(500 * time.Millisecond) } } }() time.Sleep(3 * time.Second) }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)帶有超時(shí)時(shí)間的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語(yǔ)句來(lái)監(jiān)聽Context的Done方法,如果Context超時(shí),我們將會(huì)取消goroutine的執(zhí)行。
3.2 取消操作
在某些情況下,我們需要在程序運(yùn)行過(guò)程中取消某些goroutine的執(zhí)行。使用Context可以幫助我們更好地控制goroutine的取消操作。我們可以創(chuàng)建一個(gè)帶有取消功能的Context,然后將其傳遞給goroutine。如果需要取消goroutine的執(zhí)行,我們可以使用Context的Cancel方法來(lái)取消goroutine的執(zhí)行。
下面是一個(gè)簡(jiǎn)單的示例代碼:
package main import ( "context" "fmt" "sync" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("goroutine finished") return default: fmt.Println("goroutine running") time.Sleep(500 * time.Millisecond) } } }() time.Sleep(2 * time.Second) cancel() wg.Wait() }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)帶有取消功能的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語(yǔ)句來(lái)監(jiān)聽Context的Done方法,如果Context被取消,我們將會(huì)取消goroutine的執(zhí)行。在主函數(shù)中,我們使用time.Sleep函數(shù)來(lái)模擬程序運(yùn)行過(guò)程中的某個(gè)時(shí)刻需要取消goroutine的執(zhí)行,然后調(diào)用Context的Cancel方法來(lái)取消goroutine的執(zhí)行。
3.3 資源管理
在某些情況下,我們需要對(duì)goroutine使用的資源進(jìn)行管理,以避免資源泄露或者出現(xiàn)競(jìng)爭(zhēng)條件等問(wèn)題。使用Context可以幫助我們更好地管理goroutine使用的資源。我們可以將資源與Context關(guān)聯(lián)起來(lái),然后將Context傳遞給goroutine。當(dāng)goroutine完成執(zhí)行后,我們可以使用Context來(lái)釋放資源或者進(jìn)行其他的資源管理操作。
下面是一個(gè)簡(jiǎn)單的示例代碼:
package main import ( "context" "fmt" "sync" "time" ) func worker(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("goroutine finished") return default: fmt.Println("goroutine running") time.Sleep(500 * time.Millisecond) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup wg.Add(1) go worker(ctx, &wg) time.Sleep(2 * time.Second) cancel() wg.Wait() }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)帶有取消功能的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語(yǔ)句來(lái)監(jiān)聽Context的Done方法,如果Context被取消,我們將會(huì)取消goroutine的執(zhí)行。在主函數(shù)中,我們使用time.Sleep函數(shù)來(lái)模擬程序運(yùn)行過(guò)程中的某個(gè)時(shí)刻需要取消goroutine的執(zhí)行,然后調(diào)用Context的Cancel方法來(lái)取消goroutine的執(zhí)行。
到此這篇關(guān)于Golang中常見的三種并發(fā)控制方式使用小結(jié)的文章就介紹到這了,更多相關(guān)Go并發(fā)控制方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文帶你玩轉(zhuǎn)Golang Prometheus Eexporter開發(fā)
本文分兩大塊,一是搞清楚prometheus四種類型的指標(biāo)Counter,Gauge,Histogram,Summary用golang語(yǔ)言如何構(gòu)造這4種類型對(duì)應(yīng)的指標(biāo),二是搞清楚修改指標(biāo)值的場(chǎng)景和方式,感興趣的可以了解一下2023-02-02簡(jiǎn)單談?wù)凣olang中的字符串與字節(jié)數(shù)組
這篇文章主要給大家介紹了關(guān)于Golang中字符串與字節(jié)數(shù)組的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Go語(yǔ)言中調(diào)用外部命令的方法總結(jié)
在工作中,我們時(shí)不時(shí)地會(huì)需要在Go中調(diào)用外部命令。本文為大家總結(jié)了Go語(yǔ)言中調(diào)用外部命令的幾種姿勢(shì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-11-11golang構(gòu)建HTTP服務(wù)的實(shí)現(xiàn)步驟
其實(shí)很多框架都是在 最簡(jiǎn)單的http服務(wù)上做擴(kuò)展的的,基本上都是遵循h(huán)ttp協(xié)議,本文主要介紹了golang構(gòu)建HTTP服務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Go語(yǔ)言單線程運(yùn)行也會(huì)有的并發(fā)問(wèn)題解析
這篇文章主要為大家介紹了Go語(yǔ)言單線程運(yùn)行的并發(fā)問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Mac下Vs code配置Go語(yǔ)言環(huán)境的詳細(xì)過(guò)程
這篇文章給大家介紹Mac下Vs code配置Go語(yǔ)言環(huán)境的詳細(xì)過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07Go實(shí)現(xiàn)后臺(tái)任務(wù)調(diào)度系統(tǒng)的實(shí)例代碼
平常我們?cè)陂_發(fā)API的時(shí)候,前端傳遞過(guò)來(lái)的大批數(shù)據(jù)需要經(jīng)過(guò)后端處理,如果后端處理的速度快,前端響應(yīng)就快,反之則很慢,影響用戶體驗(yàn),為了解決這一問(wèn)題,需要我們自己實(shí)現(xiàn)后臺(tái)任務(wù)調(diào)度系統(tǒng),本文將介紹如何用Go語(yǔ)言實(shí)現(xiàn)后臺(tái)任務(wù)調(diào)度系統(tǒng),需要的朋友可以參考下2023-06-06