sync 包详解
概述
sync 包提供了基本的同步原语,如互斥锁。除了 Once 和 WaitGroup 类型外,大多数原语供底层库例程使用。更高级的同步最好通过通道和通信来实现。
核心功能:
- 互斥锁(Mutex、RWMutex)
- 条件变量(Cond)
- 一次性操作(Once)
- 等待组(WaitGroup)
- 对象池(Pool)
- 并发安全的 Map(Map)
- Once 辅助函数(OnceFunc、OnceValue、OnceValues)
重要说明:
- ✅ Go 版本:所有 Go 版本都支持
- ⚠️ 禁止复制:包含 sync 类型的值不应被复制
- ⚠️ 使用场景:大多数原语供底层库使用,高层同步建议使用 channel
包导入
import "sync"
接口
Locker
type Locker interface {
Lock()
Unlock()
}
功能: 表示可以锁定和解锁的对象。
实现类型:
*Mutex*RWMutexRWMutex.RLocker()返回的接口
示例:
package main
import (
"fmt"
"sync"
)
func useLocker(locker sync.Locker) {
locker.Lock()
defer locker.Unlock()
fmt.Println("锁已获取")
}
func main() {
var mu sync.Mutex
var rwmu sync.RWMutex
useLocker(&mu)
useLocker(rwmu.RLocker()) // 只读锁
}
类型详解(按 A-Z 分类)
C
Cond
type Cond struct {
L Locker
}
功能: 实现条件变量,是等待或宣布事件发生的 goroutine 的集合点。
字段:
L Locker- 关联的锁(通常是 *Mutex 或 *RWMutex)
重要说明:
- 每个 Cond 都有一个关联的 Locker L
- 在改变条件和调用 Wait 时必须持有 L
- Cond 首次使用后不能复制
- 对于许多简单用例,使用 channel 比 Cond 更好
方法:
Broadcast()- 唤醒所有等待的 goroutineSignal()- 唤醒一个等待的 goroutineWait()- 等待直到被唤醒
示例:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu sync.Mutex
cond := sync.NewCond(&mu)
ready := false
// 启动等待者
go func() {
mu.Lock()
for !ready {
cond.Wait()
}
fmt.Println("准备就绪!")
mu.Unlock()
}()
time.Sleep(100 * time.Millisecond)
// 通知
mu.Lock()
ready = true
cond.Signal()
mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
M
Map
type Map struct {
// 包含过滤或未导出的字段
}
功能: 类似于 Go map[any]any,但对多个 goroutine 的并发使用是安全的,无需额外的锁或协调。
特点:
- 加载、存储和删除操作以分摊常数时间运行
- 零值 Map 是空的且可直接使用
- 首次使用后不能复制
优化场景:
- 给定键的条目只写入一次但读取多次(如只增长的缓存)
- 多个 goroutine 为不相交的键集读取、写入和覆盖条目
方法:
Clear()- 删除所有条目CompareAndDelete(key, old any)- 比较并删除(Go 1.20+)CompareAndSwap(key, old, new any)- 比较并交换(Go 1.20+)Delete(key any)- 删除键Load(key any)- 加载值LoadAndDelete(key any)- 加载并删除(Go 1.15+)LoadOrStore(key, value any)- 加载或存储Range(f func(key, value any) bool)- 遍历Store(key, value any)- 存储值Swap(key, value any)- 交换值
示例:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// Store
m.Store("name", "Alice")
m.Store("age", 25)
// Load
if v, ok := m.Load("name"); ok {
fmt.Println(v) // Alice
}
// LoadOrStore
v, loaded := m.LoadOrStore("age", 30)
fmt.Println(v, loaded) // 25 true
// Range
m.Range(func(key, value any) bool {
fmt.Printf("%s: %v\n", key, value)
return true
})
// Delete
m.Delete("age")
// Swap (Go 1.20+)
prev, loaded := m.Swap("name", "Bob")
fmt.Println(prev, loaded) // Alice true
}
Mutex
type Mutex struct {
// 包含过滤或未导出的字段
}
功能: 互斥锁,用于保护共享资源。
特点:
- 零值是未锁定的互斥锁
- 首次使用后不能复制
- 锁定的 Mutex 不与特定 goroutine 关联
方法:
Lock()- 锁定TryLock()- 尝试锁定Unlock()- 解锁
示例:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
counter := 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(counter) // 1000
}
Mutex.TryLock
func (m *Mutex) TryLock() bool
功能: 尝试锁定 m 并报告是否成功。
注意:
- 正确使用 TryLock 的情况很少见
- 使用 TryLock 通常是互斥锁使用中存在深层问题的标志
示例:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
if mu.TryLock() {
fmt.Println("获取锁成功")
mu.Unlock()
}
// 再次尝试
mu.Lock()
go func() {
if !mu.TryLock() {
fmt.Println("获取锁失败")
}
}()
}
O
Once
type Once struct {
// 包含过滤或未导出的字段
}
功能: 将恰好执行一次操作的对象。
特点:
- 零值 Once 表示尚未执行
- 首次使用后不能复制
- 用于必须恰好运行一次的初始化
方法:
Do(f func())- 执行函数 f(仅一次)
示例:
package main
import (
"fmt"
"sync"
)
var once sync.Once
func initOnce() {
fmt.Println("初始化")
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(initOnce)
}()
}
wg.Wait()
// 只输出一次:初始化
}
P
Pool
type Pool struct {
New func() any
}
功能: 临时对象的集合,可以单独保存和检索。
目的:
- 缓存已分配但未使用的项以供以后重用
- 减轻垃圾回收器的压力
- 构建高效、线程安全的空闲列表
字段:
New func() any- 当 Get 无法从池中获取对象时调用的函数
方法:
Get() any- 从池中获取对象Put(x any)- 将对象放回池中
注意:
- 池中存储的任何项都可能在任何时候被自动删除
- 首次使用后不能复制
示例:
package main
import (
"bytes"
"fmt"
"sync"
)
var pool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func main() {
// 从池中获取
buf := pool.Get().(*bytes.Buffer)
buf.WriteString("Hello")
fmt.Println(buf.String())
// 放回池中
buf.Reset()
pool.Put(buf)
// 再次获取(可能重用)
buf2 := pool.Get().(*bytes.Buffer)
fmt.Println("重用:", buf2)
}
R
RWMutex
type RWMutex struct {
// 包含过滤或未导出的字段
}
功能: 读写互斥锁,可以被任意数量的读取者或单个写入者持有。
特点:
- 零值是未锁定的互斥锁
- 首次使用后不能复制
- 不支持递归读锁定
- RLock 不能升级为 Lock,Lock 不能降级为 RLock
方法:
Lock()- 锁定(写)RLock()- 读锁定RLocker() Locker- 返回只读 Locker 接口RUnlock()- 解锁(读)TryLock()- 尝试锁定(写)TryRLock()- 尝试读锁定Unlock()- 解锁(写)
示例:
package main
import (
"fmt"
"sync"
)
func main() {
var rwmu sync.RWMutex
data := make(map[string]int)
// 写锁定
rwmu.Lock()
data["key"] = 42
rwmu.Unlock()
// 多个读锁定可以同时持有
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
rwmu.RLock()
fmt.Println(data["key"])
rwmu.RUnlock()
}()
}
wg.Wait()
}
RWMutex.TryLock
func (rw *RWMutex) TryLock() bool
功能: 尝试锁定 rw 用于写入并报告是否成功。
注意:
- 正确使用 TryLock 的情况很少见
- 通常是存在深层问题的标志
RWMutex.TryRLock
func (rw *RWMutex) TryRLock() bool
功能: 尝试锁定 rw 用于读取并报告是否成功。
W
WaitGroup
type WaitGroup struct {
// 包含过滤或未导出的字段
}
功能: 计数信号量,通常用于等待一组 goroutine 完成。
特点:
- 零值 WaitGroup 是空的
- 首次使用后不能复制
- 计数器为负时会 panic
方法:
Add(delta int)- 添加计数器Done()- 减少计数器(等价于 Add(-1))Go(f func())- 启动新 goroutine 并添加到 WaitGroup(Go 1.25+)Wait()- 等待直到计数器为零
使用模式:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// 模式 1:使用 Add/Done
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("任务 1")
}()
go func() {
defer wg.Done()
fmt.Println("任务 2")
}()
wg.Wait()
// 模式 2:使用 Go(Go 1.25+)
// wg.Go(func() { fmt.Println("任务 3") })
// wg.Go(func() { fmt.Println("任务 4") })
// wg.Wait()
}
函数详解(按 A-Z 分类)
N
NewCond
func NewCond(l Locker) *Cond
功能: 返回一个带有 Locker l 的新 Cond。
参数:
l Locker- 关联的锁
返回值:
*Cond- 新的条件变量
示例:
package main
import (
"sync"
)
func main() {
var mu sync.Mutex
cond := sync.NewCond(&mu)
// 使用 cond...
_ = cond
}
O
OnceFunc
func OnceFunc(f func()) func()
功能: 返回一个只调用 f 一次的函数。返回的函数可以并发调用。
参数:
f func()- 要执行的函数
返回值:
func()- 包装后的函数
注意:
- 如果 f panic,返回的函数每次调用都会 panic
示例:
package main
import (
"fmt"
"sync"
)
func main() {
f := sync.OnceFunc(func() {
fmt.Println("只执行一次")
})
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
}
wg.Wait()
// 只输出一次:只执行一次
}
OnceValue
func OnceValue[T any](f func() T) func() T
功能: 返回一个只调用 f 一次并返回 f 返回的值的函数。返回的函数可以并发调用。
参数:
f func() T- 要执行的函数
返回值:
func() T- 包装后的函数
注意:
- Go 1.21+
- 如果 f panic,返回的函数每次调用都会 panic
示例:
package main
import (
"fmt"
"sync"
)
func expensiveComputation() int {
fmt.Println("执行昂贵计算")
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
return sum
}
func main() {
getValue := sync.OnceValue(expensiveComputation)
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(getValue())
}()
}
wg.Wait()
// 只输出一次:执行昂贵计算
// 然后输出 5 次:5050
}
OnceValues
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)
功能: 返回一个只调用 f 一次并返回 f 返回的值的函数。返回的函数可以并发调用。
参数:
f func() (T1, T2)- 要执行的函数
返回值:
func() (T1, T2)- 包装后的函数
注意:
- Go 1.21+
- 如果 f panic,返回的函数每次调用都会 panic
示例:
package main
import (
"fmt"
"sync"
)
func readFile() (string, error) {
fmt.Println("读取文件")
return "file content", nil
}
func main() {
readFileOnce := sync.OnceValues(readFile)
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
content, err := readFileOnce()
fmt.Println(content, err)
}()
}
wg.Wait()
// 只输出一次:读取文件
// 然后输出 3 次:file content <nil>
}
典型示例
示例 1:使用 Mutex 保护共享资源
package main
import (
"fmt"
"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
}
func main() {
counter := &Counter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println(counter.Value()) // 1000
}
运行结果:
1000
示例 2:使用 RWMutex 优化读多写少
package main
import (
"fmt"
"sync"
)
type Cache struct {
mu sync.RWMutex
items map[string]string
}
func NewCache() *Cache {
return &Cache{items: make(map[string]string)}
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.items[key]
return val, ok
}
func (c *Cache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}
func main() {
cache := NewCache()
var wg sync.WaitGroup
// 多个读者
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if val, ok := cache.Get("key"); ok {
fmt.Printf("Reader %d: %s\n", id, val)
}
}(i)
}
// 一个写者
wg.Add(1)
go func() {
defer wg.Done()
cache.Set("key", "value")
fmt.Println("Writer: set value")
}()
wg.Wait()
}
运行结果:
Writer: set value
Reader 0: value
Reader 1: value
Reader 2: value
Reader 3: value
Reader 4: value
示例 3:使用 WaitGroup 等待多个 goroutine
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d 开始\n", id)
time.Sleep(100 * time.Millisecond)
fmt.Printf("Worker %d 完成\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("所有 worker 完成")
}
运行结果:
Worker 1 开始
Worker 2 开始
Worker 3 开始
Worker 4 开始
Worker 5 开始
Worker 1 完成
Worker 2 完成
Worker 3 完成
Worker 4 完成
Worker 5 完成
所有 worker 完成
示例 4:使用 Once 进行单次初始化
package main
import (
"fmt"
"sync"
)
var (
config map[string]string
configOnce sync.Once
)
func loadConfig() {
fmt.Println("加载配置...")
config = map[string]string{
"host": "localhost",
"port": "8080",
}
}
func getConfig() map[string]string {
configOnce.Do(loadConfig)
return config
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
cfg := getConfig()
fmt.Printf("Goroutine %d: %v\n", id, cfg)
}(i)
}
wg.Wait()
}
运行结果:
加载配置...
Goroutine 0: map[host:localhost port:8080]
Goroutine 1: map[host:localhost port:8080]
Goroutine 2: map[host:localhost port:8080]
Goroutine 3: map[host:localhost port:8080]
Goroutine 4: map[host:localhost port:8080]
示例 5:使用 Cond 实现条件等待
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu sync.Mutex
cond := sync.NewCond(&mu)
done := false
// 启动 3 个等待者
for i := 0; i < 3; i++ {
go func(id int) {
mu.Lock()
for !done {
cond.Wait()
}
fmt.Printf("Goroutine %d 被唤醒\n", id)
mu.Unlock()
}(i)
}
time.Sleep(100 * time.Millisecond)
// 通知所有等待者
mu.Lock()
done = true
cond.Broadcast()
mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
运行结果:
Goroutine 0 被唤醒
Goroutine 1 被唤醒
Goroutine 2 被唤醒
示例 6:使用 Pool 减少 GC 压力
package main
import (
"bytes"
"fmt"
"sync"
)
var bufferPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func process(data string) {
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
buf.WriteString("Processing: ")
buf.WriteString(data)
fmt.Println(buf.String())
bufferPool.Put(buf)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
process(fmt.Sprintf("data-%d", id))
}(i)
}
wg.Wait()
}
运行结果:
Processing: data-0
Processing: data-1
Processing: data-2
Processing: data-3
Processing: data-4
示例 7:使用 Map 进行并发安全的键值存储
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
// 并发写入
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
m.Store(fmt.Sprintf("key-%d", id), id*10)
}(i)
}
wg.Wait()
// 并发读取
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if val, ok := m.Load(fmt.Sprintf("key-%d", id)); ok {
fmt.Printf("key-%d: %v\n", id, val)
}
}(i)
}
wg.Wait()
// 遍历
m.Range(func(key, value any) bool {
fmt.Printf("%v: %v\n", key, value)
return true
})
}
运行结果:
key-0: 0
key-1: 10
key-2: 20
key-3: 30
key-4: 40
key-0: 0
key-1: 10
key-2: 20
key-3: 30
key-4: 40
示例 8:使用 OnceValue 缓存昂贵计算
package main
import (
"fmt"
"sync"
"time"
)
var compute = sync.OnceValue(func() int {
fmt.Println("执行昂贵计算...")
time.Sleep(100 * time.Millisecond)
return 42
})
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
result := compute()
fmt.Printf("Goroutine %d: %d\n", id, result)
}(i)
}
wg.Wait()
}
运行结果:
执行昂贵计算...
Goroutine 0: 42
Goroutine 1: 42
Goroutine 2: 42
Goroutine 3: 42
Goroutine 4: 42
最佳实践
1. 选择合适的同步原语
// ✅ 推荐:简单共享状态使用 Mutex
var mu sync.Mutex
var counter int
// ✅ 推荐:读多写少使用 RWMutex
var rwmu sync.RWMutex
var cache map[string]string
// ✅ 推荐:等待多个 goroutine 使用 WaitGroup
var wg sync.WaitGroup
// ✅ 推荐:单次初始化使用 Once
var once sync.Once
// ✅ 推荐:对象复用使用 Pool
var pool = sync.Pool{New: func() any { return &Buffer{} }}
2. 使用 defer 释放锁
// ✅ 推荐
mu.Lock()
defer mu.Unlock()
// 操作共享资源
// ❌ 不推荐:容易忘记解锁
mu.Lock()
// 操作共享资源
mu.Unlock() // 如果前面 panic,这里不会执行
3. 避免复制 sync 类型
// ✅ 推荐:使用指针
func process(m *sync.Mutex) {
m.Lock()
defer m.Unlock()
}
// ❌ 不推荐:复制 sync 类型
func process(m sync.Mutex) { // 会复制
m.Lock()
defer m.Unlock()
}
4. WaitGroup 正确使用 Add
// ✅ 推荐:在 goroutine 之前 Add
wg.Add(1)
go func() {
defer wg.Done()
// 工作
}()
// ❌ 不推荐:在 goroutine 内部 Add(可能 race)
go func() {
wg.Add(1) // 可能 Wait 已经调用
defer wg.Done()
// 工作
}()
5. Cond 使用循环检查条件
// ✅ 推荐
mu.Lock()
for !condition() {
cond.Wait()
}
// 使用条件
mu.Unlock()
// ❌ 不推荐:可能被虚假唤醒
mu.Lock()
if !condition() {
cond.Wait()
}
mu.Unlock()
与其他包配合
与 context 包配合
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
select {
case <-time.After(100 * time.Millisecond):
fmt.Printf("Worker %d 完成\n", id)
case <-ctx.Done():
fmt.Printf("Worker %d 被取消\n", id)
}
}(i)
}
wg.Wait()
}
与 channel 配合
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- job * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
var wg sync.WaitGroup
// 启动 3 个 worker
for i := 0; i < 3; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
// 发送任务
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// 等待 worker 完成
go func() {
wg.Wait()
close(results)
}()
// 收集结果
for result := range results {
fmt.Println(result)
}
}
注意事项
限制
-
禁止复制:
- 所有 sync 类型首次使用后都不能复制
- 包含 sync 类型的结构体也应避免复制
-
死锁风险:
- Mutex 重复锁定会死锁
- RWMutex 不支持递归读锁定
-
性能考虑:
- Mutex 在高竞争场景性能下降
- Pool 不保证对象一定被重用
-
Cond 使用限制:
- 必须在持有锁的情况下调用 Wait
- 必须使用循环检查条件
使用建议
-
优先使用 channel:
// ✅ 推荐:使用 channel 进行高层同步 done := make(chan struct{}) <-done // ⚠️ 仅在底层库使用 sync 原语 -
避免过度使用 TryLock:
// ⚠️ TryLock 通常是设计问题的标志 if mu.TryLock() { // ... } -
Pool 的 New 函数:
// ✅ 推荐:提供 New 函数 var pool = sync.Pool{ New: func() any { return &Buffer{} }, } // ⚠️ 不推荐:没有 New 函数可能返回 nil var pool = sync.Pool{} -
WaitGroup 重用:
// ✅ 推荐:Wait 返回后才能重用 wg.Wait() // 现在可以重用 wg wg.Add(1)
快速参考
类型速查表
| 类型 | 功能 | 主要方法 |
|---|---|---|
Cond | 条件变量 | Broadcast, Signal, Wait |
Locker | 锁接口 | Lock, Unlock |
Map | 并发 Map | Load, Store, Delete, Range |
Mutex | 互斥锁 | Lock, Unlock, TryLock |
Once | 单次执行 | Do |
Pool | 对象池 | Get, Put |
RWMutex | 读写锁 | Lock, RLock, Unlock, RUnlock |
WaitGroup | 等待组 | Add, Done, Wait, Go |
函数速查表
| 函数 | 功能 | Go 版本 |
|---|---|---|
NewCond | 创建 Cond | 所有 |
OnceFunc | 返回只执行一次的函数 | 1.21+ |
OnceValue | 返回只执行一次并返回值的函数 | 1.21+ |
OnceValues | 返回只执行一次并返回多值的函数 | 1.21+ |
常见模式
// 1. Mutex 保护共享资源
mu.Lock()
defer mu.Unlock()
// 操作共享资源
// 2. RWMutex 读多写少
rwmu.RLock()
defer rwmu.RUnlock()
// 读取
rwmu.Lock()
defer rwmu.Unlock()
// 写入
// 3. WaitGroup 等待多个 goroutine
wg.Add(1)
go func() {
defer wg.Done()
// 工作
}()
wg.Wait()
// 4. Once 单次初始化
once.Do(func() {
// 初始化代码
})
// 5. Pool 对象复用
obj := pool.Get()
// 使用 obj
pool.Put(obj)
// 6. Cond 条件等待
mu.Lock()
for !condition {
cond.Wait()
}
// 条件满足
mu.Unlock()
// 7. Map 并发安全存储
m.Store(key, value)
v, ok := m.Load(key)
m.Delete(key)
m.Range(func(k, v any) bool {
// 处理
return true
})
选择指南
| 场景 | 推荐原语 |
|---|---|
| 保护共享资源 | Mutex |
| 读多写少 | RWMutex |
| 等待多个 goroutine | WaitGroup |
| 单次初始化 | Once |
| 对象复用 | Pool |
| 条件等待 | Cond |
| 并发 Map | sync.Map |
| 高层同步 | channel |
总结
sync 包是 Go 标准库中用于并发同步的核心包。
核心优势:
- ✅ 提供基础同步原语
- ✅ 性能优秀
- ✅ 线程安全
- ✅ 支持多种同步模式
重要限制:
- ⚠️ 禁止复制 sync 类型
- ⚠️ 大多数原语供底层库使用
- ⚠️ 高层同步建议使用 channel
主要用途:
- 互斥锁(Mutex、RWMutex)
- 条件变量(Cond)
- 单次执行(Once)
- 等待组(WaitGroup)
- 对象池(Pool)
- 并发 Map(Map)
使用建议:
- 优先使用 channel 进行高层同步
- 使用 defer 释放锁
- 避免复制 sync 类型
- WaitGroup 在 goroutine 之前 Add
- Cond 使用循环检查条件
性能提示:
- RWMutex 在读多写少场景性能更好
- Pool 可以减少 GC 压力
- sync.Map 在特定场景比普通 map+Mutex 性能更好