Go語言中sync包使用方法教程
Go 語言的 sync 包提供了基本的同步原語,用于在并發(fā)編程中協(xié)調(diào) goroutine 之間的操作。
1. 互斥鎖 (Mutex)
互斥鎖用于保護(hù)共享資源,確保同一時(shí)間只有一個(gè) goroutine 可以訪問。
特點(diǎn):
- 最基本的同步原語,實(shí)現(xiàn)互斥訪問共享資源
- 有兩個(gè)方法:
Lock()和Unlock() - 不可重入,同一個(gè) goroutine 重復(fù)獲取會(huì)導(dǎo)致死鎖
- 沒有超時(shí)機(jī)制,鎖定后必須等待解鎖
- 不區(qū)分讀寫操作,所有操作都是互斥的
- 適用于共享資源競(jìng)爭(zhēng)不激烈的場(chǎng)景
- 性能高于 channel 實(shí)現(xiàn)的互斥機(jī)制
- 不保證公平性,可能導(dǎo)致饑餓問題
import (
"fmt"
"sync"
"time"
)
func main() {
var mutex sync.Mutex
counter := 0
for i := 0; i < 1000; i++ {
go func() {
mutex.Lock()
defer mutex.Unlock()
counter++
}()
}
time.Sleep(time.Second)
fmt.Println("計(jì)數(shù)器:", counter)
}
2. 讀寫鎖 (RWMutex)
當(dāng)多個(gè) goroutine 需要讀取而很少寫入時(shí),讀寫鎖比互斥鎖更高效。
特點(diǎn):
- 針對(duì)讀多寫少場(chǎng)景優(yōu)化的鎖
- 提供四個(gè)方法:
RLock()、RUnlock()、Lock()、Unlock() - 允許多個(gè)讀操作并發(fā)進(jìn)行,但寫操作是互斥的
- 寫鎖定時(shí),所有讀操作都會(huì)被阻塞
- 有讀鎖定時(shí),寫操作會(huì)等待所有讀操作完成
- 寫操作優(yōu)先級(jí)較高,防止寫?zhàn)囸I
- 內(nèi)部使用 Mutex 實(shí)現(xiàn)
- 比 Mutex 有更多開銷,但在讀多寫少場(chǎng)景下性能更高
var rwMutex sync.RWMutex
var data map[string]string = make(map[string]string)
// 讀取操作
func read(key string) string {
rwMutex.RLock()
defer rwMutex.RUnlock()
return data[key]
}
// 寫入操作
func write(key, value string) {
rwMutex.Lock()
defer rwMutex.Unlock()
data[key] = value
}
3. 等待組 (WaitGroup)
等待組用于等待一組 goroutine 完成執(zhí)行。
特點(diǎn):
- 用于協(xié)調(diào)多個(gè) goroutine 的完成
- 提供三個(gè)方法:
Add()、Done()、Wait() Add()增加計(jì)數(shù)器,參數(shù)可為負(fù)數(shù)Done()等同于Add(-1),減少計(jì)數(shù)器Wait()阻塞直到計(jì)數(shù)器歸零- 計(jì)數(shù)器不能變?yōu)樨?fù)數(shù),會(huì)導(dǎo)致 panic
- 可以重用,計(jì)數(shù)器歸零后可以再次增加
- 非常適合"扇出"模式(啟動(dòng)多個(gè)工作 goroutine 并等待全部完成)
- 不包含工作內(nèi)容信息,僅表示完成狀態(tài)
- 輕量級(jí),開銷很小
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 增加計(jì)數(shù)器
go func(id int) {
defer wg.Done() // 完成時(shí)減少計(jì)數(shù)器
fmt.Printf("工作 %d 完成\n", id)
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("所有工作已完成")
}
4. 一次性執(zhí)行 (Once)
Once 確保一個(gè)函數(shù)只執(zhí)行一次,無論有多少 goroutine 嘗試執(zhí)行它。
特點(diǎn):
- 確保某個(gè)函數(shù)只執(zhí)行一次
- 只有一個(gè)方法:
Do(func()) - 即使在多個(gè) goroutine 中調(diào)用也只執(zhí)行一次
- 常用于單例模式或一次性初始化
- 內(nèi)部使用互斥鎖和一個(gè)標(biāo)志位實(shí)現(xiàn)
- 非常輕量級(jí),幾乎沒有性能開銷
- 如果傳入的函數(shù) panic,視為已執(zhí)行
- 不能重置,一旦執(zhí)行就不能再次執(zhí)行
- 傳入不同的函數(shù)也不會(huì)再次執(zhí)行
var once sync.Once
var instance *singleton
func getInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
5. 條件變量 (Cond)
條件變量用于等待或宣布事件的發(fā)生。
特點(diǎn):
- 用于等待或通知事件發(fā)生
- 需要與互斥鎖結(jié)合使用:
sync.NewCond(&mutex) - 提供三個(gè)方法:
Wait()、Signal()、Broadcast() Wait()自動(dòng)解鎖并阻塞,被喚醒后自動(dòng)重新獲取鎖Signal()喚醒一個(gè)等待的 goroutineBroadcast()喚醒所有等待的 goroutine- 適合生產(chǎn)者-消費(fèi)者模式
- 可以避免輪詢,提高性能
- 使用相對(duì)復(fù)雜,容易出錯(cuò)
- 等待必須在獲取鎖后調(diào)用
var mutex sync.Mutex
var cond = sync.NewCond(&mutex)
var ready bool
func main() {
go producer()
// 消費(fèi)者
mutex.Lock()
for !ready {
cond.Wait() // 等待條件變?yōu)檎?
}
fmt.Println("數(shù)據(jù)已準(zhǔn)備好")
mutex.Unlock()
}
func producer() {
time.Sleep(time.Second) // 模擬工作
mutex.Lock()
ready = true
cond.Signal() // 通知一個(gè)等待的 goroutine
// 或使用 cond.Broadcast() 通知所有等待的 goroutine
mutex.Unlock()
}
6. 原子操作 (atomic)
對(duì)于簡(jiǎn)單的計(jì)數(shù)器或標(biāo)志,可以使用原子操作包而不是互斥鎖。
特點(diǎn):
- 底層的原子操作,無鎖實(shí)現(xiàn)
- 適用于簡(jiǎn)單的計(jì)數(shù)器或標(biāo)志位
- 比互斥鎖性能更高,開銷更小
- 提供多種原子操作:
Add、Load、Store、Swap、CompareAndSwap - 支持多種數(shù)據(jù)類型:int32、int64、uint32、uint64、uintptr 和指針
- 可用于實(shí)現(xiàn)自己的同步原語
- 不適合復(fù)雜的共享狀態(tài)
- 在多 CPU 系統(tǒng)上可能導(dǎo)致緩存一致性開銷
- Go 1.19 引入了新的原子類型
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var counter int64 = 0
for i := 0; i < 1000; i++ {
go func() {
atomic.AddInt64(&counter, 1)
}()
}
time.Sleep(time.Second)
fmt.Println("計(jì)數(shù)器:", atomic.LoadInt64(&counter))
}
7. Map (sync.Map)
Go 1.9 引入的線程安全的 map。
特點(diǎn):
- Go 1.9 引入的線程安全的哈希表
- 無需額外加鎖即可安全地并發(fā)讀寫
- 提供五個(gè)方法:
Store、Load、LoadOrStore、Delete、Range - 內(nèi)部使用分段鎖和原子操作優(yōu)化性能
- 適用于讀多寫少的場(chǎng)景
- 不保證遍歷的順序
- 不支持獲取元素?cái)?shù)量或判斷是否為空
- 不能像普通 map 那樣直接使用下標(biāo)語法
- 性能比加鎖的普通 map 更好,但單線程下比普通 map 慢
- 內(nèi)存開銷較大
var m sync.Map
func main() {
// 存儲(chǔ)鍵值對(duì)
m.Store("key1", "value1")
m.Store("key2", "value2")
// 獲取值
value, ok := m.Load("key1")
if ok {
fmt.Println("找到鍵:", value)
}
// 如果鍵不存在則存儲(chǔ)
m.LoadOrStore("key3", "value3")
// 刪除鍵
m.Delete("key2")
// 遍歷所有鍵值對(duì)
m.Range(func(key, value interface{}) bool {
fmt.Println(key, ":", value)
return true // 返回 false 停止遍歷
})
}
8. Pool (sync.Pool)
對(duì)象池用于重用臨時(shí)對(duì)象,減少垃圾回收壓力。
特點(diǎn):
- 用于緩存臨時(shí)對(duì)象,減少垃圾回收壓力
- 提供兩個(gè)方法:
Get()和Put() - 需要提供
New函數(shù)來創(chuàng)建新對(duì)象 - 對(duì)象可能在任何時(shí)候被垃圾回收,不保證存活
- 在 GC 發(fā)生時(shí)會(huì)清空池中的所有對(duì)象
- 不適合管理需要顯式關(guān)閉的資源(如文件句柄)
- 適合于頻繁創(chuàng)建和銷毀的對(duì)象
- 沒有大小限制,Put 總是成功的
- 每個(gè) P(處理器)有自己的本地池,減少競(jìng)爭(zhēng)
- Go 1.13 后大幅提升了性能
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process() {
// 獲取緩沖區(qū)
buffer := bufferPool.Get().(*bytes.Buffer)
buffer.Reset() // 清空以便重用
// 使用緩沖區(qū)
buffer.WriteString("hello")
// 操作完成后放回池中
bufferPool.Put(buffer)
}
9. 綜合示例
下面是一個(gè)綜合示例,展示了多個(gè)同步原語的使用:
package main
import (
"fmt"
"sync"
"time"
)
type SafeCounter struct {
mu sync.Mutex
wg sync.WaitGroup
count int
}
func main() {
counter := SafeCounter{}
// 啟動(dòng) 5 個(gè) goroutine 增加計(jì)數(shù)器
for i := 0; i < 5; i++ {
counter.wg.Add(1)
go func(id int) {
defer counter.wg.Done()
for j := 0; j < 10; j++ {
counter.mu.Lock()
counter.count++
fmt.Printf("Goroutine %d: 計(jì)數(shù)器 = %d\n", id, counter.count)
counter.mu.Unlock()
// 模擬工作
time.Sleep(100 * time.Millisecond)
}
}(i)
}
// 等待所有 goroutine 完成
counter.wg.Wait()
fmt.Println("最終計(jì)數(shù):", counter.count)
}
最佳實(shí)踐
使用 defer 解鎖:確保即使發(fā)生錯(cuò)誤也能解鎖
mu.Lock() defer mu.Unlock()
避免鎖的嵌套:容易導(dǎo)致死鎖
保持臨界區(qū)簡(jiǎn)短:鎖定時(shí)間越短越好
基準(zhǔn)測(cè)試比較:
- 對(duì)于大多數(shù)簡(jiǎn)單操作,atomic 比 Mutex 快
- RWMutex 在讀操作遠(yuǎn)多于寫操作時(shí)優(yōu)于 Mutex
- sync.Map 在高并發(fā)下比加鎖的 map 性能更好
內(nèi)存對(duì)齊:
- 原子操作需要內(nèi)存對(duì)齊
- 不正確的內(nèi)存對(duì)齊會(huì)嚴(yán)重影響性能
- 特別是在 32 位系統(tǒng)上使用 64 位原子操作
超時(shí)控制:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() done := make(chan struct{}) go func() { // 執(zhí)行可能耗時(shí)的操作 mu.Lock() // ... mu.Unlock() done <- struct{}{} }() select { case <-done: // 操作成功完成 case <-ctx.Done(): // 操作超時(shí) }
總結(jié)
到此這篇關(guān)于Go語言中sync包使用方法的文章就介紹到這了,更多相關(guān)Go語言sync包使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Go語言開發(fā)一個(gè)高并發(fā)系統(tǒng)
高并發(fā)系統(tǒng)是指能同時(shí)支持眾多用戶請(qǐng)求,處理大量并行計(jì)算的系統(tǒng),這篇文章主要為大家詳細(xì)介紹了如何使用Go語言開發(fā)一個(gè)高并發(fā)系統(tǒng),感興趣的小伙伴可以了解下2023-11-11
一百行Golang代碼實(shí)現(xiàn)簡(jiǎn)單并發(fā)聊天室
這篇文章主要為大家詳細(xì)介紹了一百行Golang代碼如何實(shí)現(xiàn)簡(jiǎn)單并發(fā)聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
go語言實(shí)現(xiàn)猜數(shù)字小游戲的方法
這篇文章主要介紹了go語言實(shí)現(xiàn)猜數(shù)字小游戲的方法,實(shí)例分析了Go語言流程判斷與處理的技巧,需要的朋友可以參考下2015-03-03
golang中接口對(duì)象的轉(zhuǎn)型兩種方式
這篇文章主要介紹了golang中接口對(duì)象的轉(zhuǎn)型方式,大家都知道接口對(duì)象的轉(zhuǎn)型有兩種方式,文中通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10

