# Go基础系列：互斥锁Mutex和读写锁RWMutex用法详述

## sync.Mutex

Go中使用sync.Mutex类型实现mutex(排他锁、互斥锁)。在源代码的sync/mutex.go文件中，有如下定义：

// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}


package main

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

// 共享变量
var (
m  sync.Mutex
v1 int
)

// 修改共享变量
// 在Lock()和Unlock()之间的代码部分是临界区
func change(i int) {
m.Lock()
time.Sleep(time.Second)
v1 = v1 + 1
if v1%10 == 0 {
v1 = v1 - 10*i
}
m.Unlock()
}

// 访问共享变量
// 在Lock()和Unlock()之间的代码部分是是临界区
m.Lock()
a := v1
m.Unlock()
return a
}

func main() {
var numGR = 21
var wg sync.WaitGroup

// 循环创建numGR个goroutine
for i := 0; i < numGR; i++ {
go func(i int) {
defer wg.Done()
change(i)
}(i)
}

wg.Wait()
}


0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> -100 -> -99
-> -98 -> -97 -> -96 -> -95 -> -94 -> -93 -> -92 -> -91 -> -260 -> -259


0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> -80 -> -79
-> -78 -> -77 -> -76 -> -75 -> -74 -> -72 -> -71 -> -230 -> -229 -> -229


### 适合sync.Mutex的数据类型

type mytype struct {
m   sync.Mutex
var int
}

x := new(mytype)


## sync.RWMutex

Go中使用sync.RWMutex类型实现读写互斥锁rwmutex。在源代码的sync/rwmutex.go文件中，有如下定义：

// A RWMutex is a reader/writer mutual exclusion lock.
// The lock can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// A RWMutex must not be copied after first use.
//
// If a goroutine holds a RWMutex for reading and another goroutine might
// call Lock, no goroutine should expect to be able to acquire a read lock
// until the initial read lock is released. In particular, this prohibits
// recursive read locking. This is to ensure that the lock eventually becomes
// available; a blocked Lock call excludes new readers from acquiring the
// lock.
type RWMutex struct {
w           Mutex  // held if there are pending writers
writerSem   uint32 // 写锁需要等待读锁释放的信号量
}


1. RWMutex是基于Mutex的，在Mutex的基础之上增加了读、写的信号量，并使用了类似引用计数的读锁数量
2. 读锁与读锁兼容，读锁与写锁互斥，写锁与写锁互斥，只有在锁释放后才可以继续申请互斥的锁
• 可以同时申请多个读锁
• 有读锁时申请写锁将阻塞，有写锁时申请读锁将阻塞
• 只要有写锁，后续申请读锁和写锁都将阻塞

func (rw *RWMutex) Lock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RLocker() Locker
func (rw *RWMutex) RUnlock()
func (rw *RWMutex) Unlock()


1. Lock()和Unlock()用于申请和释放写锁
2. RLock()和RUnlock()用于申请和释放读锁
• 一次RUnlock()操作只是对读锁数量减1，即减少一次读锁的引用计数
3. 如果不存在写锁，则Unlock()引发panic，如果不存在读锁，则RUnlock()引发panic
4. RLocker()用于返回一个实现了Lock()和Unlock()方法的Locker接口

package main

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

type secret struct {
RWM      sync.RWMutex
M        sync.Mutex
}

// 通过rwmutex写
func Change(c *secret, pass string) {
c.RWM.Lock()
fmt.Println("Change with rwmutex lock")
time.Sleep(3 * time.Second)
c.RWM.Unlock()
}

// 通过rwmutex读
func rwMutexShow(c *secret) string {
c.RWM.RLock()
fmt.Println("show with rwmutex",time.Now().Second())
time.Sleep(1 * time.Second)
defer c.RWM.RUnlock()
}

// 通过mutex读，和rwMutexShow的唯一区别在于锁的方式不同
func mutexShow(c *secret) string {
c.M.Lock()
fmt.Println("show with mutex:",time.Now().Second())
time.Sleep(1 * time.Second)
defer c.M.Unlock()
}

func main() {
// 定义一个稍后用于覆盖(重写)的函数
var show = func(c *secret) string { return "" }

// 通过变量赋值的方式，选择并重写showFunc函数
if len(os.Args) != 2 {
fmt.Println("Using sync.RWMutex!",time.Now().Second())
show = rwMutexShow
} else {
fmt.Println("Using sync.Mutex!",time.Now().Second())
show = mutexShow
}

var wg sync.WaitGroup

// 激活5个goroutine，每个goroutine都查看
// 根据选择的函数不同，showFunc()加锁的方式不同
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
}()
}

// 激活一个申请写锁的goroutine
go func() {
defer wg.Done()
}()
// 阻塞，直到所有wg.Done
wg.Wait()
}


Change()函数申请写锁，并睡眠3秒后修改数据，然后释放写锁。

rwMutexShow()函数申请读锁，并睡眠一秒后取得数据，并释放读锁。注意，rwMutexShow()中的print和return是相隔一秒钟的。

mutexShow()函数申请Mutex锁，和RWMutex互不相干。和rwMutexShow()唯一不同之处在于申请的锁不同。

main()中，先根据命令行参数数量决定运行哪一个show()。之所以能根据函数变量来赋值，是因为先定义了一个show()函数，它的函数签名和rwMutexShow()、mutexShow()的签名相同，所以可以相互赋值。

for循环中激活了5个goroutine并发运行，for瞬间激活5个goroutine后，继续执行main()代码会激活另一个用于申请写锁的goroutine。这6个goroutine的执行顺序是随机的。

Using sync.RWMutex! 58
show with rwmutex 58
Change with rwmutex lock
show with rwmutex 2
show with rwmutex 2
show with rwmutex 2
show with rwmutex 2
Go Pass: 123456 3
Go Pass: 123456 3
Go Pass: 123456 3
Go Pass: 123456 3


Using sync.Mutex! 30
Change with rwmutex lock
show with mutex: 30
show with mutex: 31