Goroutine 是 Go 的輕量執行緒。它們共享同一個位址空間,若沒有同步機制就同時讀寫,容易發生資料競賽。
目錄
重點
- 能用 channel 移交資料所有權時就不要共享寫入。
- 必須共享時,以
sync.Mutex或sync.RWMutex保護。 - 熱點計數器用
atomic較佳。
範例一 — 以 Mutex 保護共享狀態
package main
import (
"fmt"
"sync"
)
func main() {
var (
mu sync.Mutex
count int
wg sync.WaitGroup
)
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
mu.Lock()
count++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println("count:", count)
}
範例二 — 讀多寫少用 sync.RWMutex
var (
mu sync.RWMutex
data = make(map[string]int)
)
func read(k string) int {
mu.RLock()
defer mu.RUnlock()
return data[k]
}
func write(k string, v int) {
mu.Lock()
data[k] = v
mu.Unlock()
}
範例三 — 以 channel 移交所有權(避免共享寫入)
package main
import "fmt"
type msg struct{ n int }
func main() {
ch := make(chan msg)
go func() {
for i := 0; i < 3; i++ {
ch <- msg{n: i}
}
close(ch)
}()
for m := range ch {
fmt.Println(m.n)
}
}
範例四 — 原子操作計數器
var counter atomic.Int64
func incr() { counter.Add(1) }
死結與資料競賽
- 死結:彼此互等。統一鎖取得順序,不要在持鎖期間做阻塞 I/O 或 channel 傳送。
- 資料競賽:兩個 goroutine 同時存取且至少一個是寫入、且未同步。用
go test -race偵測。
Mermaid:共享記憶體 vs. 訊息傳遞
graph LR
A[goroutine A] -- lock/unlock --> M[(shared memory)]
B[goroutine B] -- lock/unlock --> M
A -. send .-> C((channel))
B <-. recv .- C
結論
- 優先使用 channel 傳遞資料;共享狀態則以鎖或原子操作保護。
- 臨界區越小越好;持鎖期間避免阻塞。
- 在 CI 啟用競賽偵測確保安全。