10.5.2 sync.RWMutex类型

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

type RWMutex struct {
    w           Mutex
    writerSem   uint32
    readerSem   uint32
    readerCount int32
    readerWait  int32
}

换句话说,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 的第一部分如下:

package main

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

var Password = secret{password: "myPassword"}

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

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

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

func Change(c *secret, pass string) {
    c.RWM.Lock()
    fmt.Println("LChange")
    time.Sleep(10 * time.Second)
    c.password = pass
    c.RWM.Unlock()
}

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

rwMutex.go 的第三部分如下:

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

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

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

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

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

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

func main() {
    var showFunction = func(c *secret) string {return ""}
    if len(os.Args) != 2 {
        fmt.Println("Using sync.RWMutex!")
        showFunction = show
    } else {
        fmt.Println("Using sync.Mutex!")
        showFunction = showWithLock
    }

    var waitGroup sync.WaitGroup
    fmt.Println("Pass:", showFunction(&Password))

rwMutex.go 的其余代码如下:

    for i := 0 ; i < 15; i++ {
        waitGroup.Add(1)
        go func() {
            defer waitGroup.Done()
            fmt.Println("Go Pass:", showFunction(&Password))
        }()

        go func(){
            waitGroup.Add(1)
            defer waitGroup.Done()
            Change(&Password, "123456")
        }()
        waitGroup.Wait()
        fmt.Println("Pass:", showFunction(&Password))
    }
}

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

$time go run rwMutex.go 10 >/dev/null

real 0m51.206s
user 0m0.130s
sys  0m0.074s
$time go run rwMutex.go >/dev/null

real 0m22.191s
user 0m0.135s
sys  0m0.071s

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

Last updated