文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

Golang中的RWMutex怎么使用

2023-07-05 19:51

关注

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

RWMutex 的整体模型

正如 RWMutex 的命名那样,它是区分了读锁和写锁的锁,所以我们可以从读和写两个方面来看 RWMutex 的模型。

下文中的 reader 指的是进行读操作的 goroutine,writer 指的是进行写操作的 goroutine。

读操作模型

我们可以用下图来表示 RWMutex 的读操作模型:

Golang中的RWMutex怎么使用

上图使用了 w.Lock,是因为 RWMutex 的实现中,写锁是使用 Mutex 来实现的。

说明:

写操作模型

我们可以用下图来表示 RWMutex 的写操作模型:

Golang中的RWMutex怎么使用

说明:

写操作的时候只能有一个 goroutine 持有 Lock,然后进入临界区,释放写锁之前,所有其他的 goroutine 都只能等待。

上图的 G1~G5 表示的是按时间顺序先后获取锁的几个 goroutine。

上面几个 goroutine 获取锁的过程是:

基本用法

RWMutex 中包含了以下的方法:

其他不常用的方法:

一个简单的例子

我们可以通过下面的例子来看一下 RWMutex 的基本用法:

package muteximport (   "sync"   "testing")var config map[string]stringvar mu sync.RWMutexfunc TestRWMutex(t *testing.T) {   config = make(map[string]string)   // 启动 10 个 goroutine 来写   var wg1 sync.WaitGroup   wg1.Add(10)   for i := 0; i < 10; i++ {      go func() {         set("foo", "bar")         wg1.Done()      }()   }   // 启动 100 个 goroutine 来读   var wg2 sync.WaitGroup   wg2.Add(100)   for i := 0; i < 100; i++ {      go func() {         get("foo")         wg2.Done()      }()   }   wg1.Wait()   wg2.Wait()}// 获取配置func get(key string) string {   // 获取读锁,可以多个 goroutine 并发读取   mu.RLock()   defer mu.RUnlock()   if v, ok := config[key]; ok {      return v   }   return ""}// 设置配置func set(key, val string) {   // 获取写锁   mu.Lock()   defer mu.Unlock()   config[key] = val}

上面的例子中,我们启动了 10 个 goroutine 来写配置,启动了 100 个 goroutine 来读配置。 这跟我们现实开发中的场景是一样的,很多时候其实是读多写少的。 如果我们在读的时候也使用互斥锁,那么就会导致读的性能非常差,因为读操作一般都不会有副作用的,但是如果使用互斥锁,那么就只能一个一个的读了。

而如果我们使用 RWMutex,那么就可以同时有多个 goroutine 来读取配置,这样就可以大大提高读的性能。 因为我们进行读操作的时候,可以多个 goroutine 并发读取,这样就可以大大提高读的性能。

RWMutex 使用的注意事项

在《深入理解 go Mutex》中,我们已经讲过了 Mutex 的使用注意事项, 其实 RWMutex 的使用注意事项也是差不多的:

源码剖析

RWMutex 的一些实现原理跟 Mutex 是一样的,比如阻塞的时候使用信号量等,在 Mutex 那一篇中已经有讲解了,这里不再赘述。 这里就 RWMutex 的实现原理进行一些简单的剖析。

RWMutex 结构体

RWMutex 的结构体定义如下:

type RWMutex struct {   w           Mutex        // 互斥锁,用于保护读写锁的状态   writerSem   uint32       // writer 信号量   readerSem   uint32       // reader 信号量   readerCount atomic.Int32 // 所有 reader 数量   readerWait  atomic.Int32 // writer 等待完成的 reader 数量}

各字段含义:

因为要区分读锁和写锁,所以在 RWMutex 中,我们需要两个信号量,一个用于实现写锁的阻塞等待,一个用于实现读锁的阻塞等待。 我们需要特别注意的是 readerCountreaderWait 这两个字段,我们可能会比较好奇,为什么有了 readerCount 这个字段, 还需要 readerWait 这个字段呢?

这是因为,我们在尝试获取写锁的时候,可能会有多个 reader 正在使用读锁,这时候我们需要知道有多少个 reader 正在使用读锁, 等待这些 reader 释放读锁之后,就获取写锁了,而 readerWait 这个字段就是用来记录这个数量的。 在 Lock 中获取写锁的时候,如果观测到 readerWait 不为 0 则会阻塞等待,直到 readerWait 为 0 之后才会真正获取写锁,然后才可以进行写操作。

读锁源码剖析

获取读锁的方法如下:

// 获取读锁func (rw *RWMutex) RLock() {   if rw.readerCount.Add(1) < 0 {      // 有 writer 在使用锁,阻塞等待 writer 完成      runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)   }}

读锁的实现很简单,先将 readerCount 加 1,如果加 1 之后的值小于 0,说明有 writer 正在使用锁,那么就需要阻塞等待 writer 完成。

释放读锁的方法如下:

// 释放读锁func (rw *RWMutex) RUnlock() {   // readerCount 减 1,如果 readerCount 小于 0 说明有 writer 在等待   if r := rw.readerCount.Add(-1); r < 0 {      // 有 writer 在等待,唤醒 writer      rw.rUnlockSlow(r)   }}// 唤醒 writerfunc (rw *RWMutex) rUnlockSlow(r int32) {   // 未 Lock 就 Unlock,panic   if r+1 == 0 || r+1 == -rwmutexMaxReaders {      fatal("sync: RUnlock of unlocked RWMutex")   }   // readerWait 减 1,返回值是新的 readerWait 值   if rw.readerWait.Add(-1) == 0 {      // 最后一个 reader 唤醒 writer      runtime_Semrelease(&rw.writerSem, false, 1)   }}

读锁的实现总结:

&middot;rwmutexMaxReaders算是一个特殊的标识,在获取写锁的时候会将readerCount的值减去rwmutexMaxReaders, 所以在其他地方可以根据 readerCount` 是否小于 0 来判断是否有 writer 正在使用锁。

写锁源码剖析

获取写锁的方法如下:

// 获取写锁func (rw *RWMutex) Lock() {   // 首先,解决与其他写入者的竞争。   rw.w.Lock()   // 向读者宣布有一个待处理的写入。   // r 就是当前还没有完成的读操作,等这部分读操作完成之后才可以获取写锁。   r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders   // 等待活跃的 reader   if r != 0 && rw.readerWait.Add(r) != 0 {      // 阻塞,等待最后一个 reader 唤醒      runtime_SemacquireRWMutex(&rw.writerSem, false, 0)   }}

释放写锁的方法如下:

// 释放写锁func (rw *RWMutex) Unlock() {   // 向 readers 宣布没有活动的 writer。   r := rw.readerCount.Add(rwmutexMaxReaders)   if r >= rwmutexMaxReaders { // r >= 0 并且 < rwmutexMaxReaders 才是正常的(r 是持有写锁期间尝试获取读锁的 reader 数量)      fatal("sync: Unlock of unlocked RWMutex")   }   // 如果有 reader 在等待写锁释放,那么唤醒这些 reader。   for i := 0; i < int(r); i++ {      runtime_Semrelease(&rw.readerSem, false, 0)   }   // 允许其他的 writer 继续进行。   rw.w.Unlock()}

写锁的实现总结:

TryRLock 和 TryLock

TryRLockTryLock 的实现都很简单,都是尝试获取读锁或者写锁,如果获取不到就返回 false,获取到了就返回 true,这两个方法不会阻塞等待。

// TryRLock 尝试锁定 rw 以进行读取,并报告是否成功。func (rw *RWMutex) TryRLock() bool {   for {      c := rw.readerCount.Load()      // 有 goroutine 持有写锁      if c < 0 {         return false      }      // 尝试获取读锁      if rw.readerCount.CompareAndSwap(c, c+1) {         return true      }   }}// TryLock 尝试锁定 rw 以进行写入,并报告是否成功。func (rw *RWMutex) TryLock() bool {   // 写锁被占用   if !rw.w.TryLock() {      return false   }   // 读锁被占用   if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) {      // 释放写锁      rw.w.Unlock()      return false   }   // 成功获取到锁   return true}

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

阅读原文内容投诉

免责声明:

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

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

软考中级精品资料免费领

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

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

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

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

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

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

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