Golang并发之RWMutex怎么使用

发布时间:2023-05-09 14:25:33 作者:zzz
来源:亿速云 阅读:97

这篇文章主要介绍“Golang并发之RWMutex怎么使用”,在日常操作中,相信很多人在Golang并发之RWMutex怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang并发之RWMutex怎么使用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

RWMutex

读写互斥锁是一种同步原语,它允许多个协程同时访问共享资源,同时确保一次只有一个协程可以修改资源。相较于互斥锁,读写互斥锁在读操作比写操作更频繁的情况下,可以带来更好的性能表现。

Go 语言中,RWMutex 是一种读写互斥锁的实现,它提供了一种简单有效的方式来管理对共享资源的并发访问。它提供了两种类型的锁:读锁写锁

1、读锁(RLock()TryRLock()RUnlock() 方法)

RWMutex 的读锁是一种共享锁,当一个协程获取了读锁后,其他协程也可以同时获取读锁,从而允许并发的读操作。

2、写锁(Lock()TryLock()Unlock() 方法)

RWMutex 的写锁是一种独占锁,当一个协程获取了写锁后,其他协程无法获取读锁或写锁,直到该协程释放写锁。在写锁未被释放之前,任何想要获取读锁或写锁的 goroutine 都会被阻塞。

Golang并发之RWMutex怎么使用

RWMutex 结构体介绍

type RWMutex struct {
   w           Mutex        
   writerSem   uint32       // 写操作等待者
   readerSem   uint32       // 读操作等待者 
   readerCount atomic.Int32 // 持有读锁的 goroutine 数量
   readerWait  atomic.Int32 // 请求写锁时,需要等待完成的读锁数量
}

RWMutex 由以下字段组成:

RWMutex 常用方法:

简单读写场景示例

package main

import (
   "fmt"
   "sync"
   "time"
)

type Counter struct {
   value   int
   rwMutex sync.RWMutex
}

func (c *Counter) GetValue() int {
   c.rwMutex.RLock()
   defer c.rwMutex.RUnlock()
   return c.value
}

func (c *Counter) Increment() {
   c.rwMutex.Lock()
   defer c.rwMutex.Unlock()
   c.value++
}
func main() {
   counter := Counter{value: 0}

   // 读操作
   for i := 0; i < 10; i++ {
      go func() {
         for {
            fmt.Println("Value: ", counter.GetValue())
            time.Sleep(time.Millisecond)
         }
      }()
   }

   // 写操作
   for {
      counter.Increment()
      time.Sleep(time.Second)
   }
}

上述代码示例中定义了一个 Counter 结构体,包含一个 value 字段和一个 sync.RWMutex 实例 rwMutex。该结构体还实现了两个方法:GetValue()Increment(),分别用于读取 value 字段的值和对 value 字段的值加一。这两个方法在访问 value 字段时,使用了读写锁来保证并发安全。

main() 函数中,首先创建了一个 Counter 实例 counter,然后启动了 10 个协程,每个协程会不断读取 counter 并打印到控制台上。同时,main() 函数也会不断对 countervalue 值加 1,每次加 1 的操作都会休眠 1 秒钟。由于使用了读写锁,多个读操作可以同时进行,而写操作则会互斥进行,保证了并发安全。

基于 RWMutex 实现一个简单的协程安全的缓存

在 Go Mutex:保护并发访问共享资源的利器 文章中,使用了 Mutex 实现了一个简单的线程安全的缓存,但并不是最优的设计,对于缓存场景,读操作比写操作更频繁,因此使用 RWMutex 代替 Mutex 会更好。

import "sync"

type Cache struct {
   data    map[string]any
   rwMutex sync.RWMutex
}

func NewCache() *Cache {
   return &Cache{
      data: make(map[string]any),
   }
}

func (c *Cache) Get(key string) (any, bool) {
   c.rwMutex.RLock()
   defer c.rwMutex.RUnlock()
   value, ok := c.data[key]
   return value, ok
}

func (c *Cache) Set(key string, value any) {
   c.rwMutex.Lock()
   defer c.rwMutex.Unlock()
   c.data[key] = value
}

上述代码实现了一个协程安全的缓存,通过使用 RWMutex 的读写锁,保证了 Get() 方法可以被多个 goroutine 并发地执行,而且只有在读操作和写操作同时存在时才会进行互斥锁定,有效地提高了并发性能。

RWMutex 易错场景

没有正确的加锁和解锁

为了正确使用读写锁,必须正确使用锁的方法。对于读操作,必须成对使用 RLock()RUnlock() 方法,否则可能会导致程序 panic 或阻塞。

例如:如果缺少 RLock(),直接使用 RUnlock()方法,程序将会 panic,如果缺少 RUnlock() 方法,将会发生阻塞的形象。

同样,对于写操作,必须成对使用 Lock()Unlock() 方法。

最佳实践是使用 defer 来释放锁:为了保证锁总是被释放,即使在运行时错误或提前返回的情况下,也可以在获得锁后立即使用 defer 关键字来调度相应的解锁方法。

rwMutex.RLock()
defer rwMutex.RUnlock()
// 读操作

rwMutex.Lock()
defer rwMutex.Unlock()
// 写操作

重复加锁

重复加锁操作被称为可重入操作。不同于其他一些编程语言的锁实现(例如 Java 的 ReentrantLock),Go 的 mutex 并不支持可重入操作。

由于 RWMutex 内部是基于 Mutex 实现的写操作互斥,如果发生了重复加锁操作,就会导致死锁。这个易错场景在上篇文章中也提到了,还给出了代码示例,感兴趣的小伙伴可以去看看。

读操作内嵌写操作

当有协程执行读操作时,请求执行写操作的协程会被阻塞。如果在读操作中嵌入写操作的代码,写操作将调用 Lock() 方法,从而导致读操作和写操作之间形成相互依赖关系。在这种情况下,读操作会等待写操作完成后才能执行 RUnlock(),而写操作则会等待读操作完成后才能被唤醒继续执行,从而导致死锁的状态。

到此,关于“Golang并发之RWMutex怎么使用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. go语言可以开发前端吗
  2. golang和python的区别是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

golang rwmutex

上一篇:.NET Core中RabbitMQ使用死信队列如何实现

下一篇:nvm怎么下载,安装与使用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》