Go 服务开发高手课
15
15
1.0x
00:00/00:00
登录|注册

07|并发安全:如何为不同并发场景选择合适的锁?

你好,我是徐逸。
上节课我们一起学习了并发等待技术。不过在实际的编程实践中,我们会遇到各种各样的并发场景,所需要的并发技术也会有所不同。今天咱们就来聊聊在并发环境下,如何巧妙地运用锁,实现高性能、安全地访问多协程共享的数据。
我们先从一个问题入手。假如我们现在需要实现一个底层用 map 类型存储数据的本地缓存,该怎么设计,才能在并发环境下高性能且安全地访问这个缓存呢?

互斥锁

对于多协程共享数据的安全访问,最简单的方案就是用互斥锁。互斥锁能保证在同一时刻,只有一个协程能够访问被保护的共享数据
在 Golang 中,并发包 sync 里面的 Mutex 类型实现了互斥锁功能。它的核心是下面两个方法。
Lock 方法,用于加锁,当锁已经被占用时,调用协程会阻塞直到锁可用。
Unlock 方法,用于释放锁。
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
知道了 Mutex 类型的方法,如同下面的代码一样,咱们可以在本地缓存的读写操作中,调用 Mutex 对象的 Lock 和 Unlock 方法来实现并发安全的本地缓存访问。
import (
"sync"
)
type MutexCache struct {
mu sync.Mutex // 互斥锁
data map[string]string // 共享数据
}
// NewMutexCache初始化一个MutexCache实例
func NewMutexCache() *MutexCache {
c := &MutexCache{data: make(map[string]string)}
return c
}
// Set更新缓存
func (c *MutexCache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
// Get从缓存中获取值
func (c *MutexCache) Get(key string) (string, bool) {
c.mu.Lock()
defer c.mu.Unlock()
value, ok := c.data[key]
return value, ok
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
  • 解释
  • 总结

1. 互斥锁和读写锁是在并发编程中常用的锁机制,用于保证共享数据的安全访问。 2. 互斥锁适用于写操作频繁的场景,而读写锁适用于读操作频繁的场景,能够提升共享数据的并发访问性能。 3. 分段锁是针对map结构的并发访问优化方案,通过将大的共享数据结构划分成多个较小的段,每个段有独立的锁,可以减少并发访问时的阻塞情况。 4. 在读多写少的场景中,读写锁的性能明显优于互斥锁,可以提升并发读取的效率。 5. 通过合适选择互斥锁、读写锁或分段锁,可以根据不同的并发场景实现高性能、安全地访问共享数据. 6. atomic包提供了对数据进行原子操作的功能,适用于无锁编程,避免加锁操作,提升性能。 7. atomic包提供了Add、CAS、Load、Store、Swap等操作,用于对整型和指针类型进行原子操作。 8. atomic包还提供了Value类型,用于对复杂类型对象进行原子存取的能力。 9. 选择合适的并发安全访问方式是在并发编程中需要注意的重点,根据不同场景选择合适的锁机制或无锁编程方式。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 服务开发高手课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(1)

  • 最新
  • 精选
  • lJ
    1. LockFreeCache虽然 atomic.Value 可以确保对整个 map 的原子性更新,但好像依然存在读写冲突问题。例如,在 Get 方法中读取了 map 的指针后,对 map 的内容操作是非原子的。如果此时另一个协程调用 Update 替换了整个 map,那么读取就可能是过时的数据了吧。如果再加互斥锁,那么还不如基于RWMutexCache实现呢。另外,调用 Update 方法时,新的 map 被存储在 atomic.Value 中,而旧的 map 仍然在内存中,会导致内存泄漏的吧。 2. 这种无所编程实现的map其对应的benchmark测试,性能如何 3. 面试中遇到过问基于channel实现的并发安全map,这个实现适用的场景是啥,是否有可替换的方法呢? 4. 思考题 type StackNode struct { value interface{} next *StackNode } type LockFreeStack struct { head unsafe.Pointer // 栈顶节点 } func NewLockFreeStack() *LockFreeStack { return &LockFreeStack{} } func (s *LockFreeStack) Push(value interface{}) { newNode := &StackNode{value: value} for { currentHead := (*StackNode)(atomic.LoadPointer(&s.head)) newNode.next = currentHead if atomic.CompareAndSwapPointer(&s.head, unsafe.Pointer(currentHead), unsafe.Pointer(newNode)) { return } } } func (s *LockFreeStack) Pop() (interface{}, bool) { for { currentHead := (*StackNode)(atomic.LoadPointer(&s.head)) if currentHead == nil { return nil, false } if atomic.CompareAndSwapPointer(&s.head, unsafe.Pointer(currentHead), unsafe.Pointer(currentHead.next)) { return currentHead.value, true } } }
    2024-12-23归属地:江苏
收起评论
显示
设置
留言
1
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部