Golang中常見的三種并發(fā)控制方式使用小結
Go語言中的goroutine是一種輕量級的線程,其優(yōu)點在于占用資源少、切換成本低,能夠高效地實現并發(fā)操作。但如何對這些并發(fā)的goroutine進行控制呢?
一提到并發(fā)控制,大家最先想到到的是鎖。Go中同樣提供了鎖的相關機制,包括互斥鎖sync.Mutex和讀寫鎖sync.RWMutex;除此之外Go還提供了原子操作sync/atomic。但這些操作都是針對并發(fā)過程中的數據安全的,并不是針對goroutine本身的。
本文主要介紹的是對goroutine并發(fā)行為的控制。在Go中最常見的有三種方式:sync.WaitGroup、channel和Context。
1. sync.WaitGroup
sync.WaitGroup是Go語言中一個非常有用的同步原語,它可以幫助我們等待一組goroutine全部完成。在以下場景中,我們通常會使用sync.WaitGroup:
- 當我們需要在主函數中等待一組goroutine全部完成后再退出程序時。
- 當我們需要在一個函數中啟動多個goroutine,并確保它們全部完成后再返回結果時。
- 當我們需要在一個函數中啟動多個goroutine,并確保它們全部完成后再執(zhí)行某個操作時。
- 當我們需要在一個函數中啟動多個goroutine,并確保它們全部完成后再關閉某個資源時。
- 當我們需要在一個函數中啟動多個goroutine,并確保它們全部完成后再退出循環(huán)時。
在使用sync.WaitGroup時,我們需要先創(chuàng)建一個sync.WaitGroup對象,然后使用它的Add方法來指定需要等待的goroutine數量。接著,我們可以使用go關鍵字來啟動多個goroutine,并在每個goroutine中使用sync.WaitGroup對象的Done方法來表示該goroutine已經完成。最后,我們可以使用sync.WaitGroup對象的Wait方法來等待所有的goroutine全部完成。
下面是一個簡單的示例,會啟動3個goroutine,分別休眠0s、1s和2s,主函數會在這3個goroutine結束后退出:
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進行同步
我們可以使用一個無緩沖的channel來實現生產者-消費者模式,其中一個goroutine負責生產數據,另一個goroutine負責消費數據。當生產者goroutine將數據發(fā)送到channel時,消費者goroutine會阻塞等待數據的到來。這樣,我們可以確保生產者和消費者之間的數據同步。
下面是一個簡單的示例代碼:
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()
}在這個示例中,我們創(chuàng)建了一個無緩沖的channel,用于在生產者goroutine和消費者goroutine之間傳遞數據。生產者goroutine將數據發(fā)送到channel中,消費者goroutine從channel中接收數據。在生產者goroutine中,我們使用time.Sleep函數來模擬生產數據的時間,在消費者goroutine中,我們使用time.Sleep函數來模擬消費數據的時間。最后,我們使用sync.WaitGroup來等待所有的goroutine全部完成。
2.2 使用有緩沖channel進行限流
我們可以使用一個有緩沖的channel來限制并發(fā)goroutine的數量。在這種情況下,我們可以將channel的容量設置為我們希望的最大并發(fā)goroutine數量。然后,在啟動每個goroutine之前,我們將一個值發(fā)送到channel中。在goroutine完成后,我們從channel中接收一個值。這樣,我們可以保證同時運行的goroutine數量不超過我們指定的最大并發(fā)數量。
下面是一個簡單的示例代碼:
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()
}在這個示例中,我們創(chuàng)建了一個帶緩沖的channel,緩沖區(qū)大小為3。然后,我們啟動了10個goroutine,在每個goroutine中,我們將一個空結構體發(fā)送到channel中,表示該goroutine已經開始執(zhí)行。在goroutine完成后,我們從channel中接收一個空結構體,表示該goroutine已經完成執(zhí)行。這樣,我們可以保證同時運行的goroutine數量不超過3。
3. Context
在Go語言中,使用Context可以幫助我們更好地控制goroutine的并發(fā)。以下是一些常見的使用Context來控制goroutine并發(fā)的方法:
3.1 超時控制
在某些情況下,我們需要對goroutine的執(zhí)行時間進行限制,以避免程序長時間阻塞或者出現死鎖等問題。使用Context可以幫助我們更好地控制goroutine的執(zhí)行時間。我們可以創(chuàng)建一個帶有超時時間的Context,然后將其傳遞給goroutine。如果goroutine在超時時間內沒有完成執(zhí)行,我們可以使用Context的Done方法來取消goroutine的執(zhí)行。
下面是一個簡單的示例代碼:
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)
}在這個示例中,我們創(chuàng)建了一個帶有超時時間的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語句來監(jiān)聽Context的Done方法,如果Context超時,我們將會取消goroutine的執(zhí)行。
3.2 取消操作
在某些情況下,我們需要在程序運行過程中取消某些goroutine的執(zhí)行。使用Context可以幫助我們更好地控制goroutine的取消操作。我們可以創(chuàng)建一個帶有取消功能的Context,然后將其傳遞給goroutine。如果需要取消goroutine的執(zhí)行,我們可以使用Context的Cancel方法來取消goroutine的執(zhí)行。
下面是一個簡單的示例代碼:
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()
}在這個示例中,我們創(chuàng)建了一個帶有取消功能的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語句來監(jiān)聽Context的Done方法,如果Context被取消,我們將會取消goroutine的執(zhí)行。在主函數中,我們使用time.Sleep函數來模擬程序運行過程中的某個時刻需要取消goroutine的執(zhí)行,然后調用Context的Cancel方法來取消goroutine的執(zhí)行。
3.3 資源管理
在某些情況下,我們需要對goroutine使用的資源進行管理,以避免資源泄露或者出現競爭條件等問題。使用Context可以幫助我們更好地管理goroutine使用的資源。我們可以將資源與Context關聯起來,然后將Context傳遞給goroutine。當goroutine完成執(zhí)行后,我們可以使用Context來釋放資源或者進行其他的資源管理操作。
下面是一個簡單的示例代碼:
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()
}在這個示例中,我們創(chuàng)建了一個帶有取消功能的Context,然后將其傳遞給goroutine。在goroutine中,我們使用select語句來監(jiān)聽Context的Done方法,如果Context被取消,我們將會取消goroutine的執(zhí)行。在主函數中,我們使用time.Sleep函數來模擬程序運行過程中的某個時刻需要取消goroutine的執(zhí)行,然后調用Context的Cancel方法來取消goroutine的執(zhí)行。
到此這篇關于Golang中常見的三種并發(fā)控制方式使用小結的文章就介紹到這了,更多相關Go并發(fā)控制方式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
一文帶你玩轉Golang Prometheus Eexporter開發(fā)
本文分兩大塊,一是搞清楚prometheus四種類型的指標Counter,Gauge,Histogram,Summary用golang語言如何構造這4種類型對應的指標,二是搞清楚修改指標值的場景和方式,感興趣的可以了解一下2023-02-02
Mac下Vs code配置Go語言環(huán)境的詳細過程
這篇文章給大家介紹Mac下Vs code配置Go語言環(huán)境的詳細過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-07-07

