sync.RWMutex类型

sync.RWMutex 类型是另外一种互斥体,它是 sync.Mutex 的改进版,在 sync 目录的 rwmutex.go 文件中定义如下:

  1. type RWMutex struct {
  2. w Mutex
  3. writerSem uint32
  4. readerSem uint32
  5. readerCount int32
  6. readerWait int32
  7. }

换句话说,sync.RWMutex 是基于 sync.Mutex 做了必要的添加和改进。

现在让我们来说说 sync.RWMutex 是如果改进 sync.Mutex 的。尽管使用 sync.RWMutex 互斥锁只允许有一个函数执行写操作,但您能有多个属于 sync.RWMutex 互斥锁的读取者。然而,有一件事您要注意:除非sync.RWMutex 互斥锁的所有读取者解锁,您不能为写操作给它上锁,这也是为了实现多互斥锁读取而付出的代价。

RLock()RUnlock() 是出于读取的目的帮您与 sync.RWMutex 互斥体工作的函数,它们分别用于上锁和解锁互斥体。当您为了写操作想要上锁和解锁一个 sync.RWMutex 互斥体时,用在 sync.Mutex 互斥体的 Lock()Unlock() 函数仍适用。因此为了读操作 RLock() 函数应该与 RUnlock() 配对调用。最后,显而易见,您不应该修改在 RLock()RUnlock() 代码块间的任何共享变量。

rwMutex.go 代码说明了 sync.RWMutex 类型的使用和通途。程序分六部分介绍,并且相同的函数有俩个稍有不同的版本。第一个为读操作使用 sync.RWMutex 互斥体,第二个为读操作使用 sync.Mutex 互斥体。这两个函数之间的不同表现会帮助您理解为了读取操作使用 sync.RWMutex 互斥体的好处更多。

rwMutex.go 的第一部分如下:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "sync"
  6. "time"
  7. )
  8. var Password = secret{password: "myPassword"}
  9. type secret struct {
  10. RWM sync.RWMutex
  11. M sync.Mutex
  12. password string
  13. }

secret 结构有一个共享变量,一个 sync.RWMutex 互斥锁和一个 sync.Mutex 互斥锁。

rwMutex.go 的第二段代码如下:

  1. func Change(c *secret, pass string) {
  2. c.RWM.Lock()
  3. fmt.Println("LChange")
  4. time.Sleep(10 * time.Second)
  5. c.password = pass
  6. c.RWM.Unlock()
  7. }

Change() 函数修改共享变量,意味着您需要使用一个排他锁,这就是使用 Lock()Unlock() 函数的原因。当做变更时离不开使用排他锁!

rwMutex.go 的第三部分如下:

  1. func show(c *secret) string {
  2. c.RWM.RLock()
  3. fmt.Println("show")
  4. time.Sleep(3 * time.Second)
  5. defer c.RWM.RUnlock()
  6. return c.password
  7. }

show() 函数使用 RLock()RUnlock() 函数是因为它的关键部分是用来读取共享变量的。因此,尽管 goroutines 能够读取共享变量,但是在不使用 Lock()Unlock() 函数的情况下,所有 goroutines 都不能修改共享变量。但是,只要有人使用互斥锁读取共享变量,Lock() 函数就会被一直阻塞。

rwMutex.go 的第四段代码如下:

  1. func showWithLock(c *secret) string {
  2. c.M.Lock()
  3. fmt.Println("showWithLock")
  4. time.Sleep(3 * time.Second)
  5. defer c.M.Unlock()
  6. return c.password
  7. }

showWithLock() 函数和 show() 函数之间唯一的不同是 showWithLock() 函数为了读操作使用了排他锁,这意味着只有一个 showWithLock() 函数能读取 secret 结构体的 password 字段。

rwMutex.go 的第五段代码如下:

  1. func main() {
  2. var showFunction = func(c *secret) string {return ""}
  3. if len(os.Args) != 2 {
  4. fmt.Println("Using sync.RWMutex!")
  5. showFunction = show
  6. } else {
  7. fmt.Println("Using sync.Mutex!")
  8. showFunction = showWithLock
  9. }
  10. var waitGroup sync.WaitGroup
  11. fmt.Println("Pass:", showFunction(&Password))

rwMutex.go 的其余代码如下:

  1. for i := 0 ; i < 15; i++ {
  2. waitGroup.Add(1)
  3. go func() {
  4. defer waitGroup.Done()
  5. fmt.Println("Go Pass:", showFunction(&Password))
  6. }()
  7. go func(){
  8. waitGroup.Add(1)
  9. defer waitGroup.Done()
  10. Change(&Password, "123456")
  11. }()
  12. waitGroup.Wait()
  13. fmt.Println("Pass:", showFunction(&Password))
  14. }
  15. }

执行 rwMutex.go 两次并使用 time(1) 命令行工具测试这个程序的两个版本将产生如下输出:

  1. $time go run rwMutex.go 10 >/dev/null
  2. real 0m51.206s
  3. user 0m0.130s
  4. sys 0m0.074s
  5. $time go run rwMutex.go >/dev/null
  6. real 0m22.191s
  7. user 0m0.135s
  8. sys 0m0.071s

注意上面命令结尾处的 > /dev/null 是为了忽略这两个命令的输出。因此,使用 sync.RWMutex 互斥体的版本要比使用 sync.Mutex 的版本快很多。