當 channel 不夠用時,sync 套件提供更傳統的鎖與同步原語。
Mutex(互斥鎖)#
import "sync"
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Mutex 使用注意事項:
- 不要重複鎖定同一個 mutex(會死鎖)
- 不要解鎖未鎖定的 mutex(會 panic)
- 建議用
defer確保解鎖
RWMutex(讀寫鎖)#
適用於讀多寫少的場景:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
// 讀操作使用讀鎖
func (c *Cache) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
// 寫操作使用寫鎖
func (c *Cache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}RWMutex 允許多個讀取者同時持有讀鎖,但寫入者要獨占。讀遠多於寫時,效能顯著優於 Mutex。
WaitGroup#
用於等待一組 goroutine 完成:
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 啟動 goroutine 前增加計數
go func(id int) {
defer wg.Done() // 完成時減少計數
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}(i)
}
wg.Wait() // 阻塞直到計數歸零
fmt.Println("All workers completed")
}WaitGroup 使用規則:
Add()必須在 goroutine 啟動之前呼叫Done()應該在 goroutine 結束時呼叫(建議用defer)Wait()阻塞直到計數歸零- 計數不能變成負數(會 panic)
Once#
確保某段程式碼只執行一次:
var (
instance *Config
once sync.Once
)
func GetConfig() *Config {
once.Do(func() {
// 只執行一次,即使多個 goroutine 同時呼叫
instance = loadConfig()
})
return instance
}Once 的實作原理
sync.Once 內部用 atomic 與 mutex 實作:
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
// 快速路徑:已執行過
if atomic.LoadUint32(&o.done) == 1 {
return
}
// 慢速路徑:競爭鎖
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}