zoukankan      html  css  js  c++  java
  • Go语言中的互斥锁和读写锁(Mutex和RWMutex)


    虽然Go语言提供channel来保证协程的通信,但是某些场景用锁来显示保证协程的安全更清晰易懂。
    Go语言中主要有两种锁,互斥锁Mutex和读写锁RWMutex,下面分别介绍一下使用方法,以及出现死锁的常见场景。

    一、Mutex(互斥锁)

    Mutex是互斥锁的意思,也叫排他锁,同一时刻一段代码只能被一个线程运行,使用只需要关注方法Lock(加锁)和Unlock(解锁)即可。
    在Lock()和Unlock()之间的代码段称为资源的临界区(critical section),是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码。

    不加锁示例

    先来一段不加群的代码,10个协程同时累加1万

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var count = 0
        var wg sync.WaitGroup
        //十个协程数量
        n := 10
        wg.Add(n)
        for i := 0; i < n; i++ {
            go func() {
                defer wg.Done()
                //1万叠加
                for j := 0; j < 10000; j++ {
                    count++
                }
            }()
        }
        wg.Wait()
        fmt.Println(count)
    }
    
    

    运行结果如下

    38532
    

    正确的结果应该是100000,这里出现了并发写入更新错误的情况

    加锁示例

    我们再添加锁,代码如下

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var count = 0
        var wg sync.WaitGroup
        var mu sync.Mutex
        //十个协程数量
        n := 10
        wg.Add(n)
        for i := 0; i < n; i++ {
            go func() {
                defer wg.Done()
                //1万叠加
                for j := 0; j < 10000; j++ {
                    mu.Lock()
                    count++
                    mu.Unlock()
                }
            }()
        }
        wg.Wait()
        fmt.Println(count)
    }
    
    

    运行结果如下,可以看到,已经看到结果变成了正确的100000

    二、RWMutex(读写锁)

    Mutex在大量并发的情况下,会造成锁等待,对性能的影响比较大。
    如果某个读操作的协程加了锁,其他的协程没必要处于等待状态,可以并发地访问共享变量,这样能让读操作并行,提高读性能。
    RWLock就是用来干这个的,这种锁在某一时刻能由什么问题数量的reader持有,或者被一个wrtier持有

    主要遵循以下规则 :

    1. 读写锁的读锁可以重入,在已经有读锁的情况下,可以任意加读锁。
    2. 在读锁没有全部解锁的情况下,写操作会阻塞直到所有读锁解锁。
    3. 写锁定的情况下,其他协程的读写都会被阻塞,直到写锁解锁。

    Go语言的读写锁方法主要有下面这种

    1. Lock/Unlock:针对写操作。
      不管锁是被reader还是writer持有,这个Lock方法会一直阻塞,Unlock用来释放锁的方法
    2. RLock/RUnlock:针对读操作
      当锁被reader所有的时候,RLock会直接返回,当锁已经被writer所有,RLock会一直阻塞,直到能获取锁,否则就直接返回,RUnlock用来释放锁的方法

    并发读示例

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        var m sync.RWMutex
        go read(&m, 1)
        go read(&m, 2)
        go read(&m, 3)
    
        time.Sleep(2 * time.Second)
    }
    
    func read(m *sync.RWMutex, i int) {
        fmt.Println(i, "reader start")
        m.RLock()
        fmt.Println(i, "reading")
        time.Sleep(1 * time.Second)
        m.RUnlock()
    
        fmt.Println(i, "reader over")
    }
    

    运行如下

    可以看到,3的读还没结束,1和2已经开始读了

    并发读写示例

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    var count = 0
    
    func main() {
        var m sync.RWMutex
        for i := 1; i <= 3; i++ {
            go write(&m, i)
        }
        for i := 1; i <= 3; i++ {
            go read(&m, i)
        }
    
        time.Sleep(1 * time.Second)
        fmt.Println("final count:", count)
    }
    
    func read(m *sync.RWMutex, i int) {
        fmt.Println(i, "reader start")
        m.RLock()
        fmt.Println(i, "reading count:", count)
        time.Sleep(1 * time.Millisecond)
        m.RUnlock()
    
        fmt.Println(i, "reader over")
    }
    
    func write(m *sync.RWMutex, i int) {
        fmt.Println(i, "writer start")
        m.Lock()
        count++
        fmt.Println(i, "writing count", count)
        time.Sleep(1 * time.Millisecond)
        m.Unlock()
    
        fmt.Println(i, "writer over")
    }
    

    运行结果如下

    如果我们可以明确区分reader和writer的协程场景,且是大师的并发读、少量的并发写,有强烈的性能需要,我们就可以考虑使用读写锁RWMutex替换Mutex

    三、死锁场景

    当两个或两个以上的进程在执行过程中,因争夺资源而处理一种互相等待的状态,如果没有外部干涉无法继续下去,这时我们称系统处于死锁或产生了死锁。
    死锁主要有以下几种场景。

    Lock/Unlock不是成对出现

    没有成对出现容易会出现死锁的情况,或者是Unlock 一个未加锁的Mutex而导致 panic,代码建议以下面紧凑的方式出现

    mu.Lock()
    defer mu.Unlock()
    

    锁被拷贝使用

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var mu sync.Mutex
        mu.Lock()
        defer mu.Unlock()
        copyTest(mu)
    }
    
    //这里复制了一个锁,造成了死锁
    func copyTest(mu sync.Mutex) {
        mu.Lock()
        defer mu.Unlock()
        fmt.Println("ok")
    }
    

    在函数外层已经加了一个Lock,在拷贝的时候又执行了一次Lock,因此这是一个永远不会获得的锁,因为外层函数的Unlock无法执行。

    循环等待

    A等待B,B等待C,C等待A,陷入了无限循环(哲学家就餐问题)

    package main
    
    import (
        "sync"
    )
    
    func main() {
        var muA, muB sync.Mutex
        var wg sync.WaitGroup
    
        wg.Add(2)
        go func() {
            defer wg.Done()
            muA.Lock()
            defer muA.Unlock()
            //A依赖B
            muB.Lock()
            defer muB.Lock()
        }()
    
        go func() {
            defer wg.Done()
            muB.Lock()
            defer muB.Lock()
            //B依赖A
            muA.Lock()
            defer muA.Unlock()
        }()
        wg.Wait()
    }
    

    以上就是Go语言的锁使用,由chenqionghe倾情整理,giao~

  • 相关阅读:
    Beans
    HDU2830
    HDU1176
    rtc关机闹钟7 jni层 com_android_server_AlarmManagerService
    rtc关机闹钟6 AlarmManagerService研究
    rtc关机闹钟5 AlarmManager研究
    数论学习_裴蜀定理
    hdu-5465-二维BIT+nim
    hdu-1892-二维BIT
    hdu-2227-dp+bit
  • 原文地址:https://www.cnblogs.com/chenqionghe/p/13919427.html
Copyright © 2011-2022 走看看