前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:说说RWMutex与Mutex的区别

面试官:说说RWMutex与Mutex的区别

作者头像
小锟哥哥
发布2022-05-10 08:59:19
4690
发布2022-05-10 08:59:19
举报
文章被收录于专栏:GoLang全栈

上一篇文章我们介绍了怎么使用 Mutex 来控制计数器在高并发情况下的准确性。

但是,很明显那种方式在性能上并不算完美。

有一种 10CM 的管道,突然被缩小到了 1CM 的感觉,非常影响性能。

那有没有更好的解决方案呢?

肯定是有的,那就是读写锁

什么是读写锁?

我们在使用 MySQL 时,在高并发情况下,也会考虑将读写进行分离;

毕竟大部分都是在读数据,写数据的时候相比读时候是非常少的。

我们在处理线程也是这样的逻辑,上一节课的计数器,我可以把读和写分离开来;

让读可以并发进行,但是写时就必须限制只能进行“单线程”操作。

RWMutex

在 Go 语言中,标准库有一个 RWMutex 模块,他就能支持读写分离。

RWMutex 对读锁不排斥,对写锁排斥,同一时刻只能有一个写锁持有但允许多个多读锁持有,因为多个读者并不会改变共享数据,但存在写者时数据会被改变,此时读者阻塞。

方法

  • Lock/Unlock:写操作时调用的方法。 如果锁已经被 reader 或者 writer 持有,那么 Lock 方法会一直阻塞,直到能获取到锁; Unlock 则是配对的释放锁的方法。
  • RLock/RUnlock:读操作时调用的方法。 如果锁已经被 writer 持有的话,RLock 方法会一直阻塞,直到能获取到锁,否则就直接返回; RUnlock 是 reader 释放锁的方法。
  • RLocker:这个方法的作用是为读操作返回一个 Locker 接口的对象。 它的 Lock 方法会调用 RWMutex 的 RLock 方法; 它的 Unlock 方法会调用 RWMutex 的 RUnlock 方法。

这里面我们用的比较多的是前两者,第三个用得不是特别多。

上代码

我们现在对上一篇文章的代码进行调整:

代码语言:javascript
复制
type DownInfo struct {
 DownCount int
 sync.RWMutex
}

func NewDownInfo() *DownInfo {
 return &DownInfo{}
}

// AddCount 使用写锁保护
func (this *DownInfo) AddCount() {
 this.Lock()
 this.DownCount = this.DownCount+1
 this.Unlock()
}

// GetCount 使用读锁保护
func (this *DownInfo) GetCount() int {
 this.RLock()
 defer this.RUnlock()
 return this.DownCount
}

我们把之前的 Mutex 换成了 RWMutex !

这样的话,我们读数据时就支持并发,而写操作依旧还是 “单线程” 的方式,性能一下就上去了!

读操作为啥还要加锁?

在高并发情况下,你并不知道你此时取的值,是否就是最新的值,所以我们需要加锁让每次取的值是最新的值。

适用场景:

如果你遇到可以明确区分 reader 和 writer goroutine 的场景,且有大量的并发读、少量的并发写,并且有强烈的性能需求,你就可以考虑使用读写锁 RWMutex 替换 Mutex。

RWMutex 原理

哎,又是原理...

读写锁的设计和实现分成三类。

  • Read-preferring:读优先的设计可以提供很高的并发性; 但是,在竞争激烈的情况下可能会导致写饥饿。 这是因为,如果有大量的读,这种设计会导致只有所有的读都释放了锁之后,写才可能获取到锁
  • Write-preferring:写优先的设计意味着,如果已经有一个 writer 在等待请求锁的话,它会阻止新来的请求锁的 reader 获取到锁,所以优先保障 writer。 当然,如果有一些 reader 已经请求了锁的话,新请求的 writer 也会等待已经存在的 reader 都释放锁之后才能获取。 所以,写优先级设计中的优先权是针对新来的请求而言的。 这种设计主要避免了 writer 的饥饿问题。
  • 不指定优先级:这种设计比较简单,不区分 reader 和 writer 优先级。 某些场景下这种不指定优先级的设计反而更有效,因为第一类优先级会导致写饥饿,第二类优先级可能会导致读饥饿,这种不指定优先级的访问不再区分读写,大家都是同一个优先级,解决了饥饿的问题。

Go 标准库中的 RWMutex 设计是 Write-preferring 方案,一个正在阻塞的 Lock 调用会排除新的 reader 请求到锁。

最容易入的坑

在使用读写锁的时候,最容易出现的坑就是 重入导致死锁

先来看一段代码:

代码语言:javascript
复制
func one(l *sync.RWMutex) {
 fmt.Println("in one")
 l.Lock()
 two(l)
 l.Unlock()
}

func two(l *sync.RWMutex) {
 l.Lock()
 fmt.Println("in two")
 l.Unlock()
}

func main() {

 l := &sync.RWMutex{}
 one(l)
}

我们在 main 方法里面创建了一个读写锁,然后传给了 one 方法,然后 one 方法里面加了一个 Lock,然后调用 two 方法,two 方面里面又重复上 Lock。

这就是 重入导致死锁

但是这种错误,还是比较容易发现了,运行会报错!

另外,「Golang全栈」读者交流群成立了,聊天学习摸鱼为主,有一群有趣有料的小伙伴在等你哦!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GoLang全栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是读写锁?
    • RWMutex
      • 方法
      • 上代码
        • 读操作为啥还要加锁?
          • 适用场景:
          • RWMutex 原理
          • 最容易入的坑
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档