在golang中,sync.mutex和sync.rwmutex用于解决并发访问共享资源时的数据竞争问题,1. sync.mutex是互斥锁,同一时间只允许一个goroutine访问临界区,适用于读写频率相近或写较多的场景,典型应用包括保护共享变量、结构体字段和配合sync.once进行一次性初始化;2. sync.rwmutex是读写锁,允许多个读操作并发执行但写操作独占,适用于读远多于写的场景如配置管理、缓存系统和状态监控,能显著提升并发性能;3. 选择时应根据读写比例决定,读写接近用mutex,读远多于写用rwmutex,但需注意rwmutex可能引发写饥饿;4. 使用时需避免重复加锁、确保锁及时释放、控制锁的粒度、避免在锁内执行i/o操作,并可结合sync.once、sync.cond、sync.pool等工具优化性能,最终目标是根据实际访问模式合理选用锁机制以保障并发安全并提升效率。

在 Golang 中,
sync库是处理并发同步的核心工具之一。当多个 goroutine 同时访问共享资源时,如果不加控制,会导致数据竞争(data race),从而引发不可预测的行为。
sync.Mutex和
sync.RWMutex是最常用的两种同步机制,用于保护共享资源的并发访问。
下面分别介绍它们的使用方法和典型应用场景。
一、sync.Mutex:互斥锁的基本使用
Mutex(Mutual Exclusion)是一种互斥锁,同一时间只允许一个 goroutine 进入临界区。
立即学习“go语言免费学习笔记(深入)”;
基本用法:
var mu sync.Mutex
var count int
func increment() {
mu.Lock()
defer mu.Unlock()
count++
}Lock()
:获取锁,如果已被其他 goroutine 持有,则阻塞。Unlock()
:释放锁,必须成对调用,通常配合defer
使用。
典型应用场景:
- 修改共享变量:如计数器、状态标志等。
- 保护结构体字段:当结构体被多个 goroutine 共享时,修改其字段需加锁。
-
初始化一次性资源:配合
sync.Once
使用(底层也依赖 Mutex)。
示例:并发安全的计数器
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}注意:读操作也要加锁,否则仍可能引发 data race。
二、sync.RWMutex:读写锁优化读多场景
RWMutex区分读锁和写锁:
- 多个 goroutine 可同时持有读锁(
RLock
)。 - 写锁(
Lock
)是独占的,且与读锁互斥。
适用于读多写少的场景,能显著提升并发性能。
基本用法:
var rwMu sync.RWMutex
var config map[string]string
func readConfig(key string) string {
rwMu.RLock()
defer rwMu.RUnlock()
return config[key]
}
func updateConfig(key, value string) {
rwMu.Lock()
defer rwMu.Unlock()
config[key] = value
}RLock()
/RUnlock()
:读锁,可多个并发读。Lock()
/Unlock()
:写锁,独占访问。
典型应用场景:
- 配置管理:配置频繁被读取,偶尔更新。
-
缓存系统:如本地内存缓存(
map
类型),读远多于写。 - 状态监控:服务状态被多个监控 goroutine 读取,主逻辑偶尔更新。
示例:并发安全的配置容器
type Config struct {
mu sync.RWMutex
data map[string]interface{}
}
func (c *Config) Get(key string) interface{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Config) Set(key string, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}在这种场景下,使用
RWMutex比
Mutex能支持更高的并发读性能。
三、Mutex 与 RWMutex 如何选择?
| 场景 | 推荐锁类型 | 原因 |
|---|---|---|
| 读写频率接近,或写较多 | @@######@@ | @@######@@ 的写锁获取成本更高,可能得不偿失 |
| 读远多于写(如 10:1 以上) | @@######@@ | 提升并发读性能 |
| 只读一次或极少写 | @@######@@ | 充分利用并发读能力 |
| 锁竞争激烈,goroutine 多 | 注意避免死锁,优先考虑粒度更细的锁 | 大锁容易成为性能瓶颈 |
小提示:
Mutex的写锁会“等待所有读锁释放”,如果读操作频繁且持续时间长,可能导致写操作饥饿。
四、使用注意事项
-
不要重复加锁:
RWMutex
不可重入,同一个 goroutine 多次RWMutex
会导致死锁。 -
及时释放锁:务必使用
RWMutex
,防止 panic 导致锁无法释放。 - 锁的粒度要小:只锁必要的代码段,避免长时间持有锁。
- 避免在锁内做 I/O 操作:如网络请求、文件读写,会显著降低并发性能。
-
组合使用 once、cond、pool:
RWMutex
库还提供Mutex
(一次性初始化)、Lock()
(条件变量)、defer Unlock()
(对象池)等工具,按需使用。
基本上就这些。在实际开发中,
sync是最常用、最稳妥的选择;而
Once在读多写少的场景下是一个有效的性能优化手段。关键是根据访问模式合理选择,并始终确保对共享资源的访问是受控的。
Cond
Pool
Mutex
RWMutex










