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

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

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

一、Mutex(互斥锁)

Mutex是互斥锁的意思,也叫排他锁,同一时刻一段代码只能被一个线程运行,使用只需要关注方法Lock(加锁)和Unlock(解锁)即可。

在Lock()和Unlock()之间的代码段称为资源的临界区(critical section),是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码。

不加锁示例

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

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()            //1万叠加            for j := 0; j < 10000; j++ {                count++            }        }()    }    wg.Wait()    fmt.Println(count)}

运行结果如下

38532

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

加锁示例

我们再添加锁,代码如下

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

运行如下

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

并发读写示例

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")}

运行结果如下

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

三、死锁场景

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

死锁主要有以下几种场景。

Lock/Unlock不是成对出现

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

mu.Lock()defer mu.Unlock()

锁被拷贝使用

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")}

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

循环等待

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()        //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~

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

你可能感兴趣的文章
mysql5.7的安装和Navicat的安装
查看>>
mysql5.7示例数据库_Linux MySQL5.7多实例数据库配置
查看>>
Mysql8 数据库安装及主从配置 | Spring Cloud 2
查看>>
mysql8 配置文件配置group 问题 sql语句group不能使用报错解决 mysql8.X版本的my.cnf配置文件 my.cnf文件 能够使用的my.cnf配置文件
查看>>
MySQL8.0.29启动报错Different lower_case_table_names settings for server (‘0‘) and data dictionary (‘1‘)
查看>>
MYSQL8.0以上忘记root密码
查看>>
Mysql8.0以上重置初始密码的方法
查看>>
mysql8.0新特性-自增变量的持久化
查看>>
Mysql8.0注意url变更写法
查看>>
Mysql8.0的特性
查看>>
MySQL8修改密码报错ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
查看>>
MySQL8修改密码的方法
查看>>
Mysql8在Centos上安装后忘记root密码如何重新设置
查看>>
Mysql8在Windows上离线安装时忘记root密码
查看>>
MySQL8找不到my.ini配置文件以及报sql_mode=only_full_group_by解决方案
查看>>
mysql8的安装与卸载
查看>>
MySQL8,体验不一样的安装方式!
查看>>
MySQL: Host '127.0.0.1' is not allowed to connect to this MySQL server
查看>>
Mysql: 对换(替换)两条记录的同一个字段值
查看>>
mysql:Can‘t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock‘解决方法
查看>>