六-2: go并发编程

Mutex

简介

  • 互斥锁,限制临界区同时只能由一个线程持有
  • Mutex实现了Locker接口,对外提供两个接口,加锁用 Lock(), 退出加锁用 UnLock()
  • Mutex不用初始化,直接声明就可以使用
  • Mutex一般可以单独使用,也可以嵌入其他结构中进行封装,对外暴露接口。

实现

type Mutex struct {
	state int32
	sema  uint32
}

在这里插入图片描述

演进

  • 初版 使用一个 flag 来表示锁是否被持有,所有 goroutine 进入队列排队执行,先来先得
    • 看似公平,但却不是最优。如果能将锁交给当前正在占用CPU的 goroutine,可以避免上下文切换。
  • 给新人机会 让新来的 goroutine 与被唤醒的 goroutine 去竞争锁
    • 修改之前的flag字段,赋予三层含义。最低位表示这个锁是否被持有,第二位表示是否有被唤醒的 goroutine,其余位用来表示正在等待此锁的 goroutine数量。
  • 多给些机会 新来的 goroutine 与被唤醒的 goroutine 如果能获取到锁,不用挂起,可以节省很多上下文切换的时间,因此加入自旋让新来的多几次机会尝试获取锁。
  • 解决饥饿 由于前边的种种机制,会带来饥饿问题,所以目前又加上了解决饥饿的方案
    • 在 state 字段第三位 添加饥饿标志
    • 正常模式和饥饿模式
      • 正常模式下,waiter都是进入先进先出队列,被唤醒的waiter会和新来的 goroutine 进行竞争。如果被唤醒的waiter超过1ms获取不到锁,那么这个锁就进入饥饿模式
      • 在饥饿模式下,该锁会退化为原始的先进先出队列,新来的goroutine只会加到队列末尾,锁释放出来后直接交给队列最前面的waiter。
      • 如果该锁已经没有其他waiter或者其他waiter等待时间小于1ms,则退出饥饿模式

易错点

  • Lock/Unlock不成对出现
    • Unlock一个为加锁的Mutex会直接 panic
  • Copy已经使用的Mutex
    • Mutex的 state 字段是有状态的,会有复制一个已经加锁的锁的可能
  • 重入
    • Mutex是不可重入锁。Mutex根本就没有记录当前持有锁的goroutine,无法重入
  • 死锁
    • 产生死锁的四个条件:互斥、持有和等待、不可剥夺、环路等待

RWMutex

简介

  • 读写锁,可以将串行读变为并发读。RWMutex在某一时刻只能由任意数量的 reader 持有或者由一个 writer 持有
  • 读写锁对外提供了5个方法。
    • Lock/Unlock:写操作时调用。如果锁已经被 reader 或者 writer持有,就会一直等待,直到获取到锁
    • RLock/RUnlock:读操作时调用的方法。如果锁已经被 writer 持有,则会一直阻塞,直到获取到锁
    • RLocker:该方法会返回一个 Locker 接口的对象,其Lock/Unlock方法均对应 RWMutex的 RLock/RUnlock
  • RWMutex不用初始化,直接声明就可以使用
  • 如果有读写明确区分,且大量并发读,少量并发写的场景,可以考虑使用RwMutex

实现

读写锁在很多语言都有实现。go语言的RWMutex是基于Mutex实现的

type RWMutex struct {
	w           Mutex  // 写优先的读写锁
	writerSem   uint32  // 写信号量
	readerSem   uint32  // 读信号量
	readerCount int32   // 当前 reader 的数量
	readerWait  int32   // 记录 writer 请求锁时,需要等待 reader 的数量
}

const rwmutexMaxReaders = 1 << 30 // reader 的最大数量

RLock/RUnlock的实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okF2YaU2-1635589681811)(evernotecid://B80DA2BC-69EE-4C34-AA73-3A22C3A7AF8C/appyinxiangcom/27926023/ENResource/p46)]

  • RLock 如果 readerCount为负数,说明当前有 writer在等待请求锁,该 reader需要阻塞等待唤醒。不为负数则成功获取到读锁
  • RUnlock 释放读锁,对readerCount减一。如果 readerCount为负数,说明当前有 writer 在等待 读锁,需要调用 rUnlockSlow 方法,检验是不是所有reader都已经释放了,如果都已经释放,就可以通过 writerSem 信号量唤醒挂起的 writer

Lock的实现

  • 一旦有 writer 获得了 RWMutex 内部的 互斥锁 Mutex,就会反转 readerCount字段,将原来的正数变为负数,表示当前已经有writer在等待该读写锁。(反转的方法是 减去 rwmutexMaxReaders 常数)
  • 如果readerCount不为0,就说明当前有持有读锁的 reader,需要吧当前的 readerCount 赋值给 readerWait 保存下来。每当一个reader 释放读锁,readerWait字段就减一,直到所有 持有读锁的 reader 都释放完,才会唤醒这个 writer

Unlock的实现

在这里插入图片描述

  • 当一个 writer 要释放锁时,会再次反转 readerCount 字段。
  • 便利释放所有等待的 reader
  • 释放内部的 mutex

易错点

  • 不可复制
    • 读写错是有状态的,内部还包含互斥锁,因此同样不可复制
  • 重入导致死锁
    • 读写锁内部基于互斥锁 实现对 writer 的并发访问,因此和Mutex一样存在重入问题
  • 释放未加锁的RWMutex
    • 同Mutex
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值