sync.RWMutex适用于读多写少场景,通过允许多个读锁、独占写锁提升性能,常用于配置中心或缓存等需强一致性的场景。

sync.RWMutex
sync.RWMutex
RLock
Lock
Unlock
这就像一个图书馆:大家都可以同时进去看书(读操作),但如果有人要重新整理书架(写操作),那所有人就得暂时出去,等他整理完了才能再进来。
实际应用中,你通常会将
sync.RWMutex
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"fmt"
"sync"
"time"
)
type Cache struct {
mu sync.RWMutex
data map[string]string
count int // 只是为了演示,计数器也需要保护
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]string),
}
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock() // 获取读锁
defer c.mu.RUnlock() // 确保读锁最终被释放
val, ok := c.data[key]
// 模拟一些计算或IO操作
time.Sleep(50 * time.Millisecond)
return val, ok
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // 获取写锁
defer c.mu.Unlock() // 确保写锁最终被释放
c.data[key] = value
c.count++
// 模拟一些计算或IO操作
time.Sleep(100 * time.Millisecond)
}
func (c *Cache) GetCount() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
func main() {
cache := NewCache()
// 启动多个读取goroutine
for i := 0; i < 5; i++ {
go func(id int) {
for j := 0; j < 10; j++ {
key := fmt.Sprintf("key%d", j%2) // 读一些固定的键
val, ok := cache.Get(key)
if ok {
fmt.Printf("Reader %d: Got %s = %s\n", id, key, val)
} else {
fmt.Printf("Reader %d: Key %s not found\n", id, key)
}
}
}(i)
}
// 启动一个写入goroutine
go func() {
for i := 0; i < 3; i++ {
key := fmt.Sprintf("key%d", i)
value := fmt.Sprintf("value%d-%d", i, time.Now().UnixNano())
cache.Set(key, value)
fmt.Printf("Writer: Set %s = %s\n", key, value)
time.Sleep(200 * time.Millisecond) // 模拟写入间隔
}
}()
// 再启动一个写入goroutine,稍微晚一点
go func() {
time.Sleep(500 * time.Millisecond)
cache.Set("key_new", "new_value")
fmt.Println("Writer: Set key_new")
}()
time.Sleep(2 * time.Second) // 等待goroutine执行完成
fmt.Printf("Final cache count: %d\n", cache.GetCount())
}
这个例子清晰地展示了读写锁的用法。读者通过
RLock
RUnlock
Lock
Unlock
defer
sync.RWMutex
这个问题其实很关键,因为不是所有并发场景都适合
RWMutex
如果你发现你的共享资源绝大部分时间都在被读取,而写入操作非常稀少,甚至只是周期性的更新,那么
sync.RWMutex
RWMutex
你可以通过Go的pprof工具进行性能分析。如果pprof报告显示
sync.Mutex
sync.RWMutex
另外,也要考虑你的数据一致性要求。
RWMutex
sync/atomic
RWMutex
sync.RWMutex
即便
RWMutex
首先,忘记解锁是新手最常犯的错误。无论是
Lock
RLock
Unlock
RUnlock
defer
其次,锁的粒度非常重要。不要把整个函数体都用一个锁包起来,尤其是在锁定的代码块中包含了耗时的操作,比如网络请求、磁盘I/O或者复杂的计算。锁定的时间越长,并发性就越差。尽量将临界区(critical section)缩小到只包含对共享资源访问的部分。如果你的
Get
再来,避免在持有读锁时尝试获取写锁。这会导致死锁。一个goroutine如果已经持有读锁,它不能再尝试获取同一个
RWMutex
关于读饥饿(Reader Starvation),理论上来说,如果写入操作非常频繁,而读操作又持续不断地请求,那么新的读者可能会因为写者一直等待而无法获取读锁。不过,Go语言的
sync.RWMutex
最佳实践方面:
defer
defer mu.Unlock()
defer mu.mu.RUnlock()
RWMutex
sync.Map
map
sync.Map
RWMutex
sync/atomic
sync/atomic
RWMutex
Mutex
sync.RWMutex
Golang在并发控制方面提供了多种工具,每种都有其适用场景。
首先是sync.Mutex
Mutex
RWMutex
Mutex
其次是sync/atomic
atomic
再者,Go语言的channel(通道)是其并发模型的核心。Go倡导“不要通过共享内存来通信,而应该通过通信来共享内存”的哲学。通过channel,你可以安全地在goroutine之间传递数据,从而避免直接共享内存带来的竞态条件。你可以用channel来构建生产者-消费者模型、工作池、信号通知等复杂的并发模式。虽然它不能直接“保护”一个共享变量,但它能通过数据流的控制,间接实现并发安全。
还有一些更高级或特定用途的机制:
sync.Once
sync.WaitGroup
WaitGroup
context
选择哪种机制,很大程度上取决于你的具体需求、并发模式以及对性能和复杂度的权衡。我个人觉得,理解每种工具的优缺点,然后根据实际情况灵活组合,才是Go并发编程的精髓。
以上就是sync.RWMutex读写锁在Golang中如何优化读多写少的并发场景的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号