博客
关于我
Go语言中的互斥锁和读写锁(Mutex和RWMutex)
阅读量:413 次
发布时间:2019-03-06

本文共 3749 字,大约阅读时间需要 12 分钟。

Go语言中的锁机制与死锁场景分析

在Go语言的并发编程中,虽然Channel机制是协程间通信的主要工具,但在某些场景下,使用锁是更直观、易于理解的选择。Go语言提供了两种主要的锁类型:互斥锁(Mutex)和读写锁(RWMutex)。本文将详细介绍这两种锁的使用方法,并分析常见的死锁场景。


一、Mutex(互斥锁)

1.1 互斥锁的基本概念

Mutex的意思是“互斥锁”,即同一时间只能有一个协程执行保护其代码的区域。使用Mutex只需要掌握Lock和Unlock两个方法即可。Lock会阻塞当前协程,直到锁被释放;Unlock则由持有锁的协程释放锁。

1.2 不加锁的示例

在没有加锁的情况下,十个协程同时对一个共享变量进行操作,可能会导致以下问题:

package mainimport (    "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()            for j := 0; j < 10000; j++ {                count++            }        }()    }    wg.Wait()    fmt.Println(count)}

问题分析:由于协程间没有互斥机制,十个协程可能同时修改count变量,导致结果远小于预期(如上述示例中结果为38532,而非100000)。

1.3 加锁的示例

在加锁后,可以避免多个协程同时修改共享变量,从而确保数据的线性化:

package mainimport (    "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()            for j := 0; j < 10000; j++ {                mu.Lock()                count++                mu.Unlock()            }        }()    }    wg.Wait()    fmt.Println(count)}

问题分析:通过Mutex锁,每个协程在加锁和解锁之间保护了count变量的修改,确保了线性化,结果为100000。


二、RWMutex(读写锁)

2.1 读写锁的优势

Mutex在大量并发场景下可能会导致高阻塞率和性能问题。RWMutex则通过支持多个读者同时持有锁,提升了读操作的并发度,特别适合读密度高、写操作少的场景。

2.2 读写锁的规则

RWMutex遵循以下规则:

  • 读锁可重入:在已经持有读锁的情况下,继续加读锁不会阻塞。
  • 写锁具有优先级:在有读锁存在时,写锁会阻塞,直到所有读锁解锁。
  • 写锁和读锁互斥:在持有写锁时,其他协程无法获取读锁或写锁。
  • 2.3 读写锁的方法

    RWMutex提供了以下方法:

    • Lock():加写锁,阻塞直到解锁。
    • Unlock():释放锁。
    • RLock():加读锁,非阻塞。
    • RUnlock():释放读锁。

    2.4 并发读示例

    以下示例显示了多个读协程如何在不影响彼此的情况下同时读取数据:

    package mainimport (    "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")}

    运行结果:可以看到,三个读协程可以并行执行,而不会互相阻塞。

    2.5 并发读写示例

    以下示例展示了读写混合场景下的性能:

    package mainimport (    "fmt"    "sync"    "time")var count = 0func 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")}

    运行结果:这种配置下,读写混合的并发效率较高,适合大部分场景的需求。


    三、死锁场景

    3.1 Lock/Unlock不是成对出现

    如果Lock和Unlock不成对出现,可能会导致资源无法释放,从而引发死锁。例如:

    mu.Lock()defer mu.Unlock()// 可能在此处发生意外,导致Unlock无法执行

    问题分析:如果在加锁后发生panic或其他异常,Unlock方法不会被执行,导致锁被死持有。

    3.2 锁被拷贝使用

    在某些场景下,锁可能被多次拷贝使用,导致锁无法被正确释放:

    package mainimport (    "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")}

    问题分析:外层函数已经加锁,在拷贝后又加锁,导致外层的Unlock无法释放锁,这是一个死锁。

    3.3 循环等待

    类似哲学家就餐问题的场景,A等待B,B等待C,C等待A:

    package mainimport (    "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()        // 依赖B        muB.Lock()        defer muB.Lock()    }()    go func() {        defer wg.Done()        muB.Lock()        defer muB.Lock()        // 依赖A        muA.Lock()        defer muA.Unlock()    }()    wg.Wait()}

    问题分析:协程A和B相互等待对方的锁,导致死锁。


    通过以上内容,可以看出,Go语言的锁机制虽然提供了强大的工具,但在使用时仍需注意潜在问题,以避免死锁等并发安全问题。

    转载地址:http://qzlkz.baihongyu.com/

    你可能感兴趣的文章
    npm切换源淘宝源的两种方法
    查看>>
    npm前端包管理工具简介---npm工作笔记001
    查看>>
    npm包管理深度探索:从基础到进阶全面教程!
    查看>>
    npm升级以及使用淘宝npm镜像
    查看>>
    npm发布包--所遇到的问题
    查看>>
    npm发布自己的组件UI包(详细步骤,图文并茂)
    查看>>
    npm和package.json那些不为常人所知的小秘密
    查看>>
    npm和yarn清理缓存命令
    查看>>
    npm和yarn的使用对比
    查看>>
    npm如何清空缓存并重新打包?
    查看>>
    npm学习(十一)之package-lock.json
    查看>>
    npm安装 出现 npm ERR! code ETIMEDOUT npm ERR! syscall connect npm ERR! errno ETIMEDOUT npm ERR! 解决方法
    查看>>
    npm安装crypto-js 如何安装crypto-js, python爬虫安装加解密插件 找不到模块crypto-js python报错解决丢失crypto-js模块
    查看>>
    npm安装教程
    查看>>
    npm报错Cannot find module ‘webpack‘ Require stack
    查看>>
    npm报错Failed at the node-sass@4.14.1 postinstall script
    查看>>
    npm报错fatal: Could not read from remote repository
    查看>>
    npm报错File to import not found or unreadable: @/assets/styles/global.scss.
    查看>>
    npm报错TypeError: this.getOptions is not a function
    查看>>
    npm报错unable to access ‘https://github.com/sohee-lee7/Squire.git/‘
    查看>>