文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Golang的锁机制与使用技巧是什么

2023-06-30 17:44

关注

本篇内容主要讲解“Golang的锁机制与使用技巧是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Golang的锁机制与使用技巧是什么”吧!

1. sync.Mutex详解

sync.Mutex是Go中的互斥锁,通过.lock()方法上锁,.unlock()方法解锁。需要注意的是,因为Go函数值传递的特点,sync.Mutex通过函数传递时,会进行一次拷贝,所以传递过去的锁是一把全新的锁,大家在使用时要注意这一点,另外sync.Mutex是非重入锁,这一点要与Java中的锁区分。

type Mutex {    state int32    sema  uint32}

上面数据结构中的state最低三位分别表示 mutexLocked、mutexWoken 和 mutexStarving,剩下的位置用来表示当前有多少个 Goroutine 等待互斥锁的释放:

32                                               3             2             1             0  |                                               |             |             |             |  |                                               |             |             |             |  v-----------------------------------------------v-------------v-------------v-------------+  |                                               |             |             |             v  |                 waitersCount                  |mutexStarving| mutexWoken  | mutexLocked |  |                                               |             |             |             |  +-----------------------------------------------+-------------+-------------+-------------+

2. RWMutex详解

type RWMutex struct {w           Mutex  // 复用互斥锁writerSem   uint32 // 写锁监听读锁释放的信号量readerSem   uint32 // 读锁监听写锁释放的信号量readerCount int32  // 当前正在执行读操作的数量readerWait  int32  // 当写操作被阻塞时,需要等待读操作完成的个数}

RLock(): 申请读锁,每次执行此函数后,会对readerCount++,此时当有写操作执行Lock()时会判断readerCount>0,就会阻塞。

RUnLock(): 解除读锁,执行readerCount–,释放信号量唤醒等待写操作的goroutine。

Lock(): 申请写锁,获取互斥锁,此时会阻塞其他的写操作。并将readerCount 置为 -1,当有读操作进来,发现readerCount = -1, 即知道有写操作在进行,阻塞。

Unlock(): 解除写锁,会先通知所有阻塞的读操作goroutine,然后才会释放持有的互斥锁。

这是由于写操作要等待读操作结束后才可以获得锁,而写操作在等待期间可能还有新的读操作持续到来,如果写操作等待所有读操作结束,很可能会一直阻塞,这种现象称之为写操作被饿死。

通过RWMutex结构体中的readerWait属性可完美解决这个问题。

当写操作到来时,会把RWMutex.readerCount值拷贝到RWMutex.readerWait中,用于标记排在写操作前面的读者个数。

前面的读操作结束后,除了会递减RWMutex.readerCount,还会递减RWMutex.readerWait值,当RWMutex.readerWait值变为0时唤醒写操作。

3. sync.Map详解

一般情况下解决并发读写 map 的思路是加一把大锁,或者把一个 map 分成若干个小 map,对 key 进行哈希,只操作相应的小 map。前者锁的粒度比较大,影响效率;后者实现起来比较复杂,容易出错。

而使用 sync.map 之后,对 map 的读写,不需要加锁。并且它通过空间换时间的方式,使用 read 和 dirty 两个 map 来进行读写分离,降低锁时间来提高效率。

type Map struct {mu Mutexread atomic.Value // readOnlydirty map[interface{}]*entrymisses int}// readOnly is an immutable struct stored atomically in the Map.read field.type readOnly struct {m       map[interface{}]*entryamended bool // true if the dirty map contains some key not in m.}type entry struct {p unsafe.Pointer // *interface{}}

Golang的锁机制与使用技巧是什么

在进行读操作的时候,会先在read中找,没有命中的话会锁住dirty并且寻找,如果找到了miss计数+1,超过阈值时将dirty赋值给read;

在进行添加操作时,直接在dirty中添加;

在进行修改操作时,先改read,再改dirty;

在进行删除操作时,将read中加上amended标记,dirty中直接删除。

4. 原子操作 atomic.Value

愿此操作的底层是靠 MESI 缓存一致性协议来维持的。

Go的 atomic.Value 需要注意应该放入只读对象。

//atomic.Value源码type Value struct {v interface{} // 所以可以存储任何类型的数据}// 空 interface{} 的内部表示格式,作用是将interface{}类型分解,得到其中两个字段type ifaceWords struct {typ  unsafe.Pointerdata unsafe.Pointer}// 取数据就是正常走流程func (v *Value) Load() (x interface{}) {vp := (*ifaceWords)(unsafe.Pointer(v))typ := LoadPointer(&vp.typ)if typ == nil || uintptr(typ) == ^uintptr(0) {// 第一次还没写入return nil}  // 构造新的interface{}返回出去data := LoadPointer(&vp.data)xp := (*ifaceWords)(unsafe.Pointer(&x))xp.typ = typxp.data = datareturn}// 写数据(如何保证数据完整性)func (v *Value) Store(x interface{}) {if x == nil {panic("sync/atomic: store of nil value into Value")}  // 绕过 Go 语言类型系统的检查,与任意的指针类型互相转换vp := (*ifaceWords)(unsafe.Pointer(v)) // 旧值xp := (*ifaceWords)(unsafe.Pointer(&x)) // 新值for { // 配合CompareAndSwap达到乐观锁的功效typ := LoadPointer(&vp.typ)if typ == nil { // 第一次写入runtime_procPin() // 禁止抢占if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {runtime_procUnpin() // 没有抢到锁,说明已经有别的线程抢先完成赋值,重新进入循环continue}// 首次赋值StorePointer(&vp.data, xp.data)StorePointer(&vp.typ, xp.typ)runtime_procUnpin() // 写入成功,解除占用状态return}if uintptr(typ) == ^uintptr(0) {// 第一次写入还未完成,继续等待continue}// 两次需要写入相同类型if typ != xp.typ {panic("sync/atomic: store of inconsistently typed value into Value")}StorePointer(&vp.data, xp.data)return}}// 禁止抢占,标记当前G在M上不会被抢占,并返回当前所在P的ID。func runtime_procPin()// 解除G的禁止抢占状态,之后G可被抢占。func runtime_procUnpin()

5. 使用小技巧

var m sync.Mutexfunc DoSth() {    // do sth2    func() {       u.lock()       defer m.unlock()       // do sth3    }()     // do sth4}

如上所示,如果do sth4中是很费时的io操作,使用这个技巧可以将临界区减小,提高性能,不过,如果本身临界区就不大,锁操作后续没有什么费时操作,那么也就没有必要这样操作了。

在高并发场景下,用锁的数量来换取并发效率,类似于java中ConcurrentHashmap的分段锁思想,增加锁的数量,减少一把锁控制的数据量。

在读多写少的情景下,可以使用读写锁,提高读操作的并发性能。

原子操作是CPU指令级的操作,不会触发g调度机制,不阻塞执行流。

到此,相信大家对“Golang的锁机制与使用技巧是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯