Golang 中互斥锁与读写锁的简单使用

简述

Golang中的锁机制主要包含互斥锁和读写锁

互斥锁

Gosync.Mutex

一个简单的示例:

func mutex()  {
    var mu sync.Mutex
    mu.Lock()
    fmt.Println("locked")
    mu.Unlock()
}
deferUnlock
func mutex()  {
    var mu sync.Mutex
    mu.Lock()
    defer mu.Unlock()
    fmt.Println("locked")
}
sync.Mutex
var mu sync.Mutex
成对出现锁加锁
func mutex()  {
    var mu sync.Mutex
    mu.Lock()
    fmt.Println("parent locked")
    mu.Lock()
    fmt.Println("sub locked")
    mu.Unlock()
    mu.Unlock()
}
fatal error: all goroutines are asleep - deadlock!
fatal error: sync: unlock of unlocked mutex
func mutex()  {
    var mu sync.Mutex
    mu.Lock()
    fmt.Println("locked")
    mu.Unlock()
    mu.Unlock()
}
goroutine
func mutex()  {
    var mu sync.Mutex
    fmt.Println("parent lock start")
    mu.Lock()
    fmt.Println("parent locked")
    for i := 0; i <= 2; i++ {
        go func(i int) {
            fmt.Printf("sub(%d) lock start\n", i)
            mu.Lock()
            fmt.Printf("sub(%d) locked\n", i)
            time.Sleep(time.Microsecond * 30)
            mu.Unlock()
            fmt.Printf("sub(%d) unlock\n", i)
        }(i)
    }
    time.Sleep(time.Second * 2)
    mu.Unlock()
    fmt.Println("parent unlock")
    time.Sleep(time.Second * 2)
}

先看上面的函数执行结果

parent lock start
parent locked
sub(0) lock start
sub(2) lock start
sub(1) lock start
parent unlock // 必须等到父级先解锁,后面则会阻塞
sub(0) locked // 解锁后子goroutine才能执行锁定
sub(0) unlock
sub(2) locked
sub(2) unlock
sub(1) locked
sub(1) unlock
time.Sleep()goroutinegoroutineLockmainUnlockgoroutine

总结:

goroutinegoroutinegoroutine

读写锁

读写锁和互斥锁不同之处在于,可以分别针对读操作和写操作进行分别锁定,这样对于性能有一定的提升。 读写锁,对于多个写操作,以及写操作和读操作之前都是互斥的这一点基本等同于互斥锁。 但是对于同时多个读操作之前却非互斥关系,这也是相读写锁性能高于互斥锁的主要原因。

读写锁也是开箱即用型的

var rwm = sync.RWMutex

读写锁分为写锁和读锁:

rwm.Lock()
rwm.Unlock()
rwm.RLock()
rwm.RUnlock()
panic
func rwMutex()  {
    var rwm sync.RWMutex

    rwm.Lock()
    fmt.Println("locked")
    rwm.RUnlock()
}
fatal error: sync: RUnlock of unlocked RWMutex

对于读写锁,同一资源可以同时有多个读锁定,如:

func rwMutex()  {
    var rwm sync.RWMutex

    rwm.RLock()
    rwm.RLock()
    rwm.RLock()
    fmt.Println("locked")
    rwm.RUnlock()
    rwm.RUnlock()
    rwm.RUnlock()
}
deadlockpanic
func rwMutex()  {
    var rwm sync.RWMutex

    rwm.Lock()
    rwm.Lock()
    rwm.Lock()
    fmt.Println("locked")
    rwm.Unlock()
    rwm.Unlock()
    rwm.Unlock()
}
goroutinegoroutine
goroutine

下面看一个完整示例:

func rwMutex() {
    var rwm sync.RWMutex

    for i := 0; i <= 2; i++ {
        go func(i int) {
            fmt.Printf("go(%d) start lock\n", i)
            rwm.RLock()
            fmt.Printf("go(%d) locked\n", i)
            time.Sleep(time.Second * 2)
            rwm.RUnlock()
            fmt.Printf("go(%d) unlock\n", i)
        }(i)
    }
    // 先sleep一小会,保证for的goroutine都会执行
    time.Sleep(time.Microsecond * 100)
    fmt.Println("main start lock")
    // 当子进程都执行时,且子进程所有的资源都已经Unlock了
    // 父进程才会执行
    rwm.Lock()
    fmt.Println("main locked")
    time.Sleep(time.Second)
    rwm.Unlock()
}
go(0) start lock
go(0) locked
go(1) start lock
go(1) locked
go(2) start lock
go(2) locked
main start lock
go(2) unlock
go(0) unlock
go(1) unlock
main locked
goroutinemainsleep 100msgoroutinegoroutinesleep 2smaingoroutine

再看一个读锁定示例:

func rwMutex5() {
    var rwm sync.RWMutex

    for i := 0; i <= 2; i++ {
        go func(i int) {
            fmt.Printf("go(%d) start lock\n", i)
            rwm.RLock()
            fmt.Printf("go(%d) locked\n", i)
            time.Sleep(time.Second * 2)
            rwm.RUnlock()
            fmt.Printf("go(%d) unlock\n", i)
        }(i)
    }

    fmt.Println("main start lock")
    rwm.RLock()
    fmt.Println("main locked")
    time.Sleep(time.Second * 10)
}
main start lock
main locked
go(1) start lock
go(1) locked
go(2) start lock
go(2) locked
go(0) start lock
go(0) locked
go(0) unlock
go(1) unlock
go(2) unlock
goroutine

总结:

goroutine