每個(gè) Go 開發(fā)者都應(yīng)該知道的 goroutine 模式
Go 是為并發(fā)編程而設(shè)計(jì)的。它輕量、高效,并簡(jiǎn)化了創(chuàng)建并行應(yīng)用程序的過程,內(nèi)置支持 goroutine 和通道。讓我們深入了解 Go 中的主要并發(fā)概念,包括它們的定義、示例、優(yōu)勢(shì)以及有效使用的技巧。

Goroutines
goroutine 是由 Go 運(yùn)行時(shí)調(diào)度的輕量級(jí)線程,創(chuàng)建成本遠(yuǎn)低于操作系統(tǒng)線程,可在單一進(jìn)程中啟動(dòng)成千上萬的并發(fā)任務(wù)。
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go printNumbers()
fmt.Println("Goroutine started!")
time.Sleep(3 * time.Second)
}優(yōu)勢(shì):
- 極低內(nèi)存占用,適合 I/O 密集或需大量等待的任務(wù)
- 自動(dòng)調(diào)度,充分利用多核處理器
通道(channel)
通道是一種類型安全的通信機(jī)制,用于在 goroutine 之間傳遞數(shù)據(jù)并確保同步。
package main
import "fmt"
func sum(nums []int, resultChan chan int) {
sum := 0
for _, num := range nums {
sum += num
}
resultChan <- sum
}
func main() {
nums := []int{1, 2, 3, 4, 5}
resultChan := make(chan int)
go sum(nums, resultChan)
result := <-resultChan
fmt.Println("Sum:", result)
}優(yōu)點(diǎn):通道提供了一種強(qiáng)大的方式來同步數(shù)據(jù)傳輸,簡(jiǎn)化了通信并防止數(shù)據(jù)競(jìng)爭(zhēng)。通過確保數(shù)據(jù)僅在準(zhǔn)備好時(shí)發(fā)送或接收,通道有助于避免典型的同步問題。
Channel Axioms
Channel Axioms 定義了通道行為的規(guī)則:
- 對(duì)無緩沖通道,發(fā)送與接收雙方在對(duì)方就緒前均會(huì)阻塞。
- 關(guān)閉通道后不可再發(fā)送數(shù)據(jù),違者將觸發(fā) panic。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
for val := range ch {
fmt.Println(val)
}
}優(yōu)點(diǎn):理解 Channel Axioms 有助于避免在 Go 中常見的陷阱,如死鎖和競(jìng)爭(zhēng)條件。通過了解阻塞和關(guān)閉通道的工作原理,開發(fā)人員可以設(shè)計(jì)出可靠的并發(fā)應(yīng)用程序。
基于通道的錯(cuò)誤處理
在使用通道時(shí),處理可能發(fā)生的錯(cuò)誤至關(guān)重要,尤其是在多個(gè) goroutine 共享數(shù)據(jù)或資源時(shí)。
package main
import (
"errors"
"fmt"
)
func divide(dividend, divisor int, resultChan chan int, errorChan chan error) {
if divisor == 0 {
errorChan <- errors.New("cannot divide by zero")
return
}
resultChan <- dividend / divisor
}
func main() {
resultChan := make(chan int)
errorChan := make(chan error)
go divide(10, 0, resultChan, errorChan)
select {
case result := <-resultChan:
fmt.Println("Result:", result)
case err := <-errorChan:
fmt.Println("Error:", err)
}
}優(yōu)點(diǎn):基于通道的錯(cuò)誤處理使得對(duì)并發(fā)任務(wù)的細(xì)粒度控制成為可能,使程序能夠有效地管理和響應(yīng)錯(cuò)誤。
選擇語(yǔ)句
select 允許 goroutine 同時(shí)等待多個(gè)通信操作,先就緒者先執(zhí)行。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "from channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from channel 2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}優(yōu)點(diǎn):select 使非阻塞并發(fā)成為可能,程序可以在不等待所有通道的情況下繼續(xù)執(zhí)行,從而提高響應(yīng)速度。
使用 WaitGroups 協(xié)調(diào)并發(fā)
sync.WaitGroup 有助于管理多個(gè) goroutine,確保它們?cè)诔绦蚶^續(xù)執(zhí)行之前全部完成。
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}優(yōu)點(diǎn):WaitGroups 提供了一種簡(jiǎn)單的方法來協(xié)調(diào) goroutines,確保適當(dāng)?shù)耐讲⒎乐钩绦蜻^早退出。
上下文包(Context)
Go 中的context包對(duì)于管理 goroutine 的生命周期非常有用,特別是在取消、截止日期和超時(shí)方面。
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context) {
select {
case <-time.After(5 * time.Second):
fmt.Println("Work completed")
case <-ctx.Done():
fmt.Println("Work canceled")
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go work(ctx)
time.Sleep(3 * time.Second)
}優(yōu)點(diǎn):上下文對(duì)于管理具有眾多 goroutine 的復(fù)雜應(yīng)用程序是無價(jià)的,提供了一種結(jié)構(gòu)化的方式來處理取消、截止日期和層次結(jié)構(gòu)。
扇出和扇入(Fan-Out / Fan-In)
在扇出/扇入模式中,任務(wù)被分配到多個(gè) goroutine 中,并聚集回一個(gè)中央 goroutine。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, out chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
out <- id
}
func main() {
out := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, out, &wg)
}
go func() {
wg.Wait()
close(out)
}()
for result := range out {
fmt.Println("Result:", result)
}
}優(yōu)點(diǎn):Fan-In/Fan-Out 最大化并發(fā)性,非常適合數(shù)據(jù)處理任務(wù)或多個(gè) goroutine 執(zhí)行反饋到中心過程的子任務(wù)的情況。
資源池:sync.Pool
sync.Pool 通過池化可重用資源來幫助管理,從而減少內(nèi)存分配的負(fù)擔(dān)。
package main
import (
"fmt"
"sync"
)
func main() {
pool := sync.Pool{
New: func() interface{} {
return "new"
},
}
pool.Put("first")
fmt.Println(pool.Get())
fmt.Println(pool.Get())
}優(yōu)點(diǎn):減少垃圾收集并改善內(nèi)存管理,尤其在高負(fù)載或頻繁訪問的資源中非常有用。
單次初始化:sync.Once
sync.Once 結(jié)構(gòu)確保一個(gè)函數(shù)只執(zhí)行一次,無論有多少個(gè) goroutine 嘗試調(diào)用它。
package main
import (
"fmt"
"sync"
"time"
)
var once sync.Once
func initialize() {
fmt.Println("Initialization done")
}
func main() {
for i := 0; i < 3; i++ {
go once.Do(initialize)
}
time.Sleep(1 * time.Second)
}優(yōu)點(diǎn):有助于資源初始化,這種初始化只應(yīng)發(fā)生一次,防止冗余設(shè)置并確保線程安全。
數(shù)據(jù)競(jìng)爭(zhēng)與互斥
當(dāng)兩個(gè) goroutine 同時(shí)訪問同一個(gè)變量,其中至少一個(gè)在寫入時(shí),且沒有適當(dāng)?shù)耐?,就?huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)。
package main
import (
"fmt"
"sync"
)
func main() {
var count int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
count++
}()
}
wg.Wait()
fmt.Println("Count:", count)
}使用go run -race運(yùn)行此代碼會(huì)顯示競(jìng)爭(zhēng)條件。通過使用互斥鎖來修正它:
var mu sync.Mutex
mu.Lock()
count++
mu.Unlock()優(yōu)點(diǎn):防止數(shù)據(jù)競(jìng)爭(zhēng)確保一致性和穩(wěn)定性,這對(duì)具有共享數(shù)據(jù)的并發(fā)進(jìn)程的應(yīng)用程序至關(guān)重要。
結(jié)論
利用 goroutine、channel 及同步原語(yǔ),Go 能夠高效發(fā)揮多核并行能力。遵循最佳實(shí)踐、妥善管理資源和錯(cuò)誤,可構(gòu)建高性能且易維護(hù)的并發(fā)應(yīng)用。




























