Golang中常見的三種并發(fā)控制方式使用小結(jié)
Go語言中的goroutine是一種輕量級的線程,其優(yōu)點(diǎn)在于占用資源少、切換成本低,能夠高效地實(shí)現(xiàn)并發(fā)操作。但如何對這些并發(fā)的goroutine進(jìn)行控制呢?
一提到并發(fā)控制,大家最先想到到的是鎖。Go中同樣提供了鎖的相關(guān)機(jī)制,包括互斥鎖sync.Mutex和讀寫鎖sync.RWMutex;除此之外Go還提供了原子操作sync/atomic。但這些操作都是針對并發(fā)過程中的數(shù)據(jù)安全的,并不是針對goroutine本身的。
本文主要介紹的是對goroutine并發(fā)行為的控制。在Go中最常見的有三種方式:sync.WaitGroup、channel和Context。
1. sync.WaitGroup
sync.WaitGroup是Go語言中一個(gè)非常有用的同步原語,它可以幫助我們等待一組goroutine全部完成。在以下場景中,我們通常會(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對象,然后使用它的Add方法來指定需要等待的goroutine數(shù)量。接著,我們可以使用go關(guān)鍵字來啟動(dòng)多個(gè)goroutine,并在每個(gè)goroutine中使用sync.WaitGroup對象的Done方法來表示該goroutine已經(jīng)完成。最后,我們可以使用sync.WaitGroup對象的Wait方法來等待所有的goroutine全部完成。
下面是一個(gè)簡單的示例,會(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語言中,使用channel可以幫助我們更好地控制goroutine的并發(fā)。以下是一些常見的使用channel來控制goroutine并發(fā)的方法:
2.1 使用無緩沖channel進(jìn)行同步
我們可以使用一個(gè)無緩沖的channel來實(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ù)的到來。這樣,我們可以確保生產(chǎn)者和消費(fèi)者之間的數(shù)據(jù)同步。
下面是一個(gè)簡單的示例代碼:
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è)無緩沖的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ù)來模擬生產(chǎn)數(shù)據(jù)的時(shí)間,在消費(fèi)者goroutine中,我們使用time.Sleep函數(shù)來模擬消費(fèi)數(shù)據(jù)的時(shí)間。最后,我們使用sync.WaitGroup來等待所有的goroutine全部完成。
2.2 使用有緩沖channel進(jìn)行限流
我們可以使用一個(gè)有緩沖的channel來限制并發(fā)goroutine的數(shù)量。在這種情況下,我們可以將channel的容量設(shè)置為我們希望的最大并發(fā)goroutine數(shù)量。然后,在啟動(dòng)每個(gè)goroutine之前,我們將一個(gè)值發(fā)送到channel中。在goroutine完成后,我們從channel中接收一個(gè)值。這樣,我們可以保證同時(shí)運(yùn)行的goroutine數(shù)量不超過我們指定的最大并發(fā)數(shù)量。
下面是一個(gè)簡單的示例代碼:
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ù)量不超過3。
3. Context
在Go語言中,使用Context可以幫助我們更好地控制goroutine的并發(fā)。以下是一些常見的使用Context來控制goroutine并發(fā)的方法:
3.1 超時(shí)控制
在某些情況下,我們需要對goroutine的執(zhí)行時(shí)間進(jìn)行限制,以避免程序長時(shí)間阻塞或者出現(xiàn)死鎖等問題。使用Context可以幫助我們更好地控制goroutine的執(zhí)行時(shí)間。我們可以創(chuàng)建一個(gè)帶有超時(shí)時(shí)間的Context,然后將其傳遞給goroutine。如果goroutine在超時(shí)時(shí)間內(nèi)沒有完成執(zhí)行,我們可以使用Context的Done方法來取消goroutine的執(zhí)行。
下面是一個(gè)簡單的示例代碼:
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語句來監(jiān)聽Context的Done方法,如果Context超時(shí),我們將會(huì)取消goroutine的執(zhí)行。
3.2 取消操作
在某些情況下,我們需要在程序運(yùn)行過程中取消某些goroutine的執(zhí)行。使用Context可以幫助我們更好地控制goroutine的取消操作。我們可以創(chuàng)建一個(gè)帶有取消功能的Context,然后將其傳遞給goroutine。如果需要取消goroutine的執(zhí)行,我們可以使用Context的Cancel方法來取消goroutine的執(zhí)行。
下面是一個(gè)簡單的示例代碼:
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語句來監(jiān)聽Context的Done方法,如果Context被取消,我們將會(huì)取消goroutine的執(zhí)行。在主函數(shù)中,我們使用time.Sleep函數(shù)來模擬程序運(yùn)行過程中的某個(gè)時(shí)刻需要取消goroutine的執(zhí)行,然后調(diào)用Context的Cancel方法來取消goroutine的執(zhí)行。
3.3 資源管理
在某些情況下,我們需要對goroutine使用的資源進(jìn)行管理,以避免資源泄露或者出現(xiàn)競爭條件等問題。使用Context可以幫助我們更好地管理goroutine使用的資源。我們可以將資源與Context關(guān)聯(lián)起來,然后將Context傳遞給goroutine。當(dāng)goroutine完成執(zhí)行后,我們可以使用Context來釋放資源或者進(jìn)行其他的資源管理操作。
下面是一個(gè)簡單的示例代碼:
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語句來監(jiān)聽Context的Done方法,如果Context被取消,我們將會(huì)取消goroutine的執(zhí)行。在主函數(shù)中,我們使用time.Sleep函數(shù)來模擬程序運(yùn)行過程中的某個(gè)時(shí)刻需要取消goroutine的執(zhí)行,然后調(diào)用Context的Cancel方法來取消goroutine的執(zhí)行。
到此這篇關(guān)于Golang中常見的三種并發(fā)控制方式使用小結(jié)的文章就介紹到這了,更多相關(guān)Go并發(fā)控制方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文帶你玩轉(zhuǎn)Golang Prometheus Eexporter開發(fā)
本文分兩大塊,一是搞清楚prometheus四種類型的指標(biāo)Counter,Gauge,Histogram,Summary用golang語言如何構(gòu)造這4種類型對應(yīng)的指標(biāo),二是搞清楚修改指標(biāo)值的場景和方式,感興趣的可以了解一下2023-02-02
簡單談?wù)凣olang中的字符串與字節(jié)數(shù)組
這篇文章主要給大家介紹了關(guān)于Golang中字符串與字節(jié)數(shù)組的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
golang構(gòu)建HTTP服務(wù)的實(shí)現(xiàn)步驟
其實(shí)很多框架都是在 最簡單的http服務(wù)上做擴(kuò)展的的,基本上都是遵循h(huán)ttp協(xié)議,本文主要介紹了golang構(gòu)建HTTP服務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
Go語言單線程運(yùn)行也會(huì)有的并發(fā)問題解析
這篇文章主要為大家介紹了Go語言單線程運(yùn)行的并發(fā)問題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Mac下Vs code配置Go語言環(huán)境的詳細(xì)過程
這篇文章給大家介紹Mac下Vs code配置Go語言環(huán)境的詳細(xì)過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07
Go實(shí)現(xiàn)后臺任務(wù)調(diào)度系統(tǒng)的實(shí)例代碼
平常我們在開發(fā)API的時(shí)候,前端傳遞過來的大批數(shù)據(jù)需要經(jīng)過后端處理,如果后端處理的速度快,前端響應(yīng)就快,反之則很慢,影響用戶體驗(yàn),為了解決這一問題,需要我們自己實(shí)現(xiàn)后臺任務(wù)調(diào)度系統(tǒng),本文將介紹如何用Go語言實(shí)現(xiàn)后臺任務(wù)調(diào)度系統(tǒng),需要的朋友可以參考下2023-06-06

