Golang 语言中基础同步原语 Mutex 和 RWMutex 的区别

简介: Golang 语言中基础同步原语 Mutex 和 RWMutex 的区别

介绍

Golang 语言天生支持并发,关于并发编程,Golang 语言还有一句口号:“不要通过共享内存进行通信;而是通过通信共享内存”。

但是,通过“共享内存进行通信”的方式作为并发编程的解决方案在传统的编程语言中更为流行。在 Golang 语言标准库 sync 包中也提供了“通过共享内存进行通信”的并发编程解决方案。

其中,在 sync 包中最重要的同步工具就是 sync.Mutexsync.RWMutex。因为在之前的文章已经介绍过二者的使用,所以本文我们不再赘述。本文主要介绍使用二者的注意事项和二者的区别。

Mutex

Mutex 也称为互斥锁,互斥锁就是互相排斥的锁,它可以用作保护临界区的共享资源,保证同一时刻只有一个 goroutine 操作临界区中的共享资源。互斥锁 Mutex 类型有两个方法,LockUnlock

使用互斥锁的注意事项:

  • Mutex 类型变量的零值是一个未锁定状态的互斥锁。
  • Mutex 在首次被使用之后就不能再被拷贝(Mutex 是值类型,拷贝会同时拷贝互斥锁的状态)。
  • Mutex 在未锁定状态(还未锁定或已被解锁),调用 Unlock 方法,将会引发运行时错误。
  • Mutex 的锁定状态与特定 goroutine 没有关联,Mutex 被一个 goroutine 锁定, 可以被另外一个 goroutine 解锁。(不建议使用,必须使用时需要格外小心。)
  • Mutex 的 Lock 方法和 Unlock 方法要成对使用,不要忘记将锁定的互斥锁解锁,一般做法是使用 defer。

互斥锁源码:

type Mutex struct {
 state int32 // 互斥锁的状态
 sema  uint32 // 信号量,用于控制互斥锁的状态
}

03

RWMutex

RWMutex 也称为读写互斥锁,读写互斥锁就是读取/写入互相排斥的锁。它可以由任意数量的读取操作的 goroutine 或单个写入操作的 goroutine 持有。读写互斥锁 RWMutex 类型有五个方法,LockUnlockRlockRUnlockRLocker。其中,RLocker 返回一个 Locker 接口,该接口通过调用 rw.RLockrw.RUnlock 来实现 Lock 和 Unlock 方法。

使用读写互斥锁的注意事项:

  • RWMutex 类型变量的零值是一个未锁定状态的互斥锁。
  • RWMutex 在首次被使用之后就不能再被拷贝。
  • RWMutex 的读锁或写锁在未锁定状态,解锁操作都会引发 panic。
  • RWMutex 的一个写锁 Lock 去锁定临界区的共享资源,如果临界区的共享资源已被(读锁或写锁)锁定,这个写锁操作的 goroutine 将被阻塞直到解锁。
  • RWMutex 的读锁不要用于递归调用,比较容易产生死锁。
  • RWMutex 的锁定状态与特定的 goroutine 没有关联。一个 goroutine 可以 RLock(Lock),另一个 goroutine 可以 RUnlock(Unlock)。
  • 写锁被解锁后,所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒,并都可以成功锁定读锁。
  • 读锁被解锁后,在没有被其他读锁锁定的前提下,所有因操作锁定写锁而被阻塞的 goroutine,其中等待时间最长的一个 goroutine 会被唤醒。

读写互斥锁源码:

type RWMutex struct {
 w           Mutex  // held if there are pending writers
 writerSem   uint32 // semaphore for writers to wait for completing readers
 readerSem   uint32 // semaphore for readers to wait for completing writers
 readerCount int32  // number of pending readers
 readerWait  int32  // number of departing readers
}

04

Mutex 和 RWMutex 的区别

RWMutex 和 Mutex 的区别是 RWMutex 将对临界区的共享资源的读写操作做了区分,RWMutex 可以针对读写操作做不同级别的锁保护。

RWMutex 读写锁中包含读锁和写锁,它的 LockUnlock 方法用作写锁保护,它的 RLockRUnlock 方法用作读锁保护。

RWMutex 读写锁中的读锁和写锁关系如下:

  • 在写锁处于锁定状态时,操作锁定读锁的 goroutine 会被阻塞。
  • 在写锁处于锁定状态时,操作锁定写锁的 goroutine 会被阻塞。
  • 在读锁处于锁定状态时,操作锁定写锁的 goroutine 会被阻塞。

但是,在读锁处于锁定状态时,操作锁定读锁的 goroutine 不会被阻塞。我们可以理解为读锁保护的临界区的共享资源,多个读操作可以同时执行。

05

总结

本文我们介绍了 Golang 语言中的基本同步原语互斥锁和读写互斥锁使用时的注意事项,然后总结了二者的区别。读写互斥锁可以对临界区的共享资源做更加细粒度的访问控制,在读锁持有锁时,其他操作读锁的 goroutine 不被被阻塞,(也就是说不限制对临界区的共享资源的并发读)所以在读多写少的场景,我们可以使用读写互斥锁替代互斥锁,提升应用程序的性能。

推荐阅读:

Golang 语言的编程技巧之变量

Golang 语言中的非类型安全指针

参考资料:

https://golang.org/doc/effective_go#concurrency 

https://golang.org/pkg/sync/#Mutex 

https://golang.org/pkg/sync/#RWMutex 


目录
相关文章
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
114 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
67 4
Golang语言文件操作快速入门篇
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
104 3
Golang语言之gRPC程序设计示例
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
14天前
|
Go 计算机视觉
在Golang高并发环境中如何进行协程同步?
在此示例中,使用互斥锁来保护对共享计数器变量 c 的访问,确保并发的 HTTP 请求不会产生数据竞争。
37 3
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
52 4
Golang语言goroutine协程篇
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
55 3
Golang语言之Prometheus的日志模块使用案例
|
1月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
31 0
|
2月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
48 3
|
2月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
64 3
Golang语言之函数(func)进阶篇