Go并发同步核心库syn包的使用深度指南
作者:程序员爱钓鱼
在 Go 语言中,并发是最重要的特性之一。通过 goroutine 可以轻松启动成千上万个并发任务,但随之而来的问题是:如何安全地共享数据、控制执行顺序以及避免竞态条件。这正是 sync 包存在的意义。
sync 是 Go 标准库中用于 并发控制与同步 的核心工具,它提供了一系列原语(primitives),用于协调多个 goroutine 之间的执行关系。常见组件包括:Mutex、RWMutex、WaitGroup、Once、Cond、Pool 等。
相比 channel,sync 更适合处理共享内存并发模型,尤其是在需要高性能或细粒度控制时。
互斥锁:sync.Mutex
最基础的同步工具是 Mutex(互斥锁),用于保证同一时间只有一个 goroutine 可以访问共享资源。
package main
import (
"fmt"
"sync"
)
var (
count int
mu sync.Mutex
)
func add() {
mu.Lock()
count++
mu.Unlock()
}
func main() {
for i := 0; i < 1000; i++ {
go add()
}
// 简单等待(真实项目应使用 WaitGroup)
fmt.Scanln()
fmt.Println(count)
}
如果没有加锁,多个 goroutine 同时修改 count,会导致数据错误(竞态条件)。加锁后可以保证数据安全。
需要注意的是:
Lock()和Unlock()必须成对出现- 通常推荐使用
defer mu.Unlock()防止遗漏
读写锁:sync.RWMutex
当读操作远多于写操作时,可以使用读写锁优化性能。
var rw sync.RWMutex // 读 rw.RLock() value := count rw.RUnlock() // 写 rw.Lock() count++ rw.Unlock()
特点:
- 多个读可以并发执行
- 写操作是独占的
适用于:
- 缓存系统
- 配置读取
- 高读低写场景
等待组:sync.WaitGroup
WaitGroup 用于等待一组 goroutine 执行完成,是并发编程中最常用的工具之一。
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("worker", id)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("all done")
}
执行流程:
Add(n)设置任务数量- 每个 goroutine 完成后调用
Done() Wait()阻塞直到所有任务完成
这是 Go 中控制并发任务结束的标准方式。
只执行一次:sync.Once
Once 用于保证某段代码只执行一次,常用于初始化操作。
var once sync.Once
func initConfig() {
fmt.Println("初始化配置")
}
func main() {
for i := 0; i < 3; i++ {
go once.Do(initConfig)
}
fmt.Scanln()
}
无论调用多少次 Do(),函数只会执行一次。
适用于:
- 单例模式
- 配置加载
- 资源初始化
条件变量:sync.Cond
Cond 用于在 goroutine 之间进行条件通知,类似“等待-唤醒”机制。
var mu sync.Mutex
cond := sync.NewCond(&mu)
ready := false
go func() {
cond.L.Lock()
for !ready {
cond.Wait()
}
fmt.Println("开始执行")
cond.L.Unlock()
}()
// 模拟准备完成
cond.L.Lock()
ready = true
cond.Signal()
cond.L.Unlock()
常见方法:
Wait() 等待条件 Signal() 唤醒一个 Broadcast() 唤醒所有
适用于:
- 生产者消费者模型
- 任务调度系统
对象池:sync.Pool
sync.Pool 用于缓存临时对象,减少内存分配,提高性能。
var pool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func main() {
buf := pool.Get().([]byte)
// 使用 buf
pool.Put(buf)
}
特点:
- 自动回收(受 GC 管理)
- 减少频繁分配内存
适用于:
- 高频创建销毁对象
- 日志系统
- 序列化处理
Map 并发安全:sync.Map
Go 原生 map 不是并发安全的,sync.Map 提供了并发安全的 map。
var m sync.Map
m.Store("key", "value")
v, ok := m.Load("key")
fmt.Println(v, ok)
常用方法:
Store Load Delete Range
适用于:
- 读多写少场景
- 缓存系统
sync 与 channel 对比
Go 并发有两种主流方式:
channel sync
对比:
| 方式 | 特点 |
|---|---|
| channel | 通信优先 |
| sync | 共享内存控制 |
简单理解:
- channel:通过通信共享数据
- sync:通过共享内存同步数据
实际开发中,两者通常结合使用。
常见使用场景
在实际项目中,sync 广泛应用于:
- 并发计数器(Mutex)
- 任务调度(WaitGroup)
- 单例初始化(Once)
- 缓存系统(RWMutex / sync.Map)
- 高性能对象复用(Pool)
例如实现一个并发安全计数器:
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Add() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
常见错误
忘记 Unlock:
mu.Lock() // 没有 Unlock
导致死锁。
WaitGroup 使用错误:
wg.Add(1)
wg.Wait()
go func() {
wg.Done()
}()
可能 panic。
复制 sync 对象:
mu2 := mu // 错误
sync 类型不能复制。
使用建议
实际开发中推荐:
- 简单同步用 Mutex
- 读多写少用 RWMutex
- 等待任务用 WaitGroup
- 初始化用 Once
- 高性能缓存用 Pool
- 并发 map 用 sync.Map
同时注意:
- 尽量避免锁粒度过大
- 避免死锁
- 合理设计并发结构
总结
sync 是 Go 并发编程的核心工具库,它提供了一整套用于 协调 goroutine、保护共享数据、控制执行顺序 的机制。
核心组件包括:
- Mutex / RWMutex:数据安全
- WaitGroup:任务同步
- Once:单次执行
- Cond:条件通知
- Pool:性能优化
- sync.Map:并发 map
在高并发系统、Web 服务、任务调度器、缓存系统等场景中,sync 都是不可或缺的基础工具。熟练掌握 sync,可以让你的 Go 程序在并发安全和性能方面达到更高水平。
以上就是Go并发同步核心库syn包的使用深度指南的详细内容,更多关于Go syn并发同步的资料请关注脚本之家其它相关文章!
