Go并发编程-读写锁 - Go语言中文社区

Go并发编程-读写锁


一、前言

go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁、CAS、原子变量操作类。本节我们先来看看go中读写锁,读写锁相比互斥锁来说并发度更高,在读多写少情况下比较实用。

二、读写锁

在go中可以使用sync.RWMutex获取一个读写锁,读写锁是读写互斥锁,读锁可以被任意多的读goroutine持有,但是写锁只能被某一个goroutine持有。当一个goroutine获取读锁后,其他goroutine也可以获取到读锁,但是如果这时候有goroutine尝试获取写锁,则获取写锁的线程将会被阻塞,这时候如果再有goroutine尝试获取读锁,则其也会被阻塞。

当某个goroutine获取到写锁后,其后尝试获取读锁的goroutine都会被阻塞。

另外读锁是可重入锁,也就是同一个goroutine在可以在持有读锁的情况下再次获取读锁。

在go中获取读锁和写锁方式如下:

var rwLock sync.RWMutex
rwLock.RLock()//获取读锁
rwLock.RUnlock()//释放读锁
rwLock.Lock()//获取写锁
rwLock.Unlock()//释放写锁

三、读写锁例子

3.1 验证多个goroutine可以同时获取读锁

首先我们来验证下多个goroutine可以同时获取读锁:

package main
import (
    "fmt"
    "sync"
)
var (
    wg      sync.WaitGroup //信号量
)
var rwlock sync.RWMutex //读写锁
func main() {
    wg.Add(1)
    //1.
    rwlock.RLock()
    fmt.Println("--main goroutine get rlock---")
    //2.
    go func() {
        rwlock.RLock()//2.1
        fmt.Println("--new goroutine get rlock---")
        rwlock.RUnlock()//2.2
        wg.Done()//2.3
    }()
    wg.Wait()//2.4
    rwlock.RUnlock()//2.5
}
  • 如上代码首先创建了一个读写锁rwlock,和一个同步用的wg对象

  • main函数所在goroutine内代码(1)获取到了读锁,然后开启了一个新的goroutine,新goroutine内首先获取读锁,然后在释放,最后让信号量减去1.

  • main函数所在goroutine在代码2.4等待新goroutine运行结束然后在释放读锁

  • 这里假设同时只有一个goroutine可以获取读锁,则由于main所在goroutine已经在代码1获取到了读锁,所以新goroutine的代码2.1必然被阻塞,所以代码2.3得不到执行,所以main所在gouroutine会一直阻塞到步骤2.4,这两个goroutine就产生了死锁状态。

  • 而实际运行上面代码程序是可正常结束的,这验证了在main所在goroutine获取读锁后释放读锁前,新goroutine也获取到了读锁,也就是多个goroutine可以同时获取读锁。

3.2 读写锁是互斥锁

本节我们来验证当一个goroutine获取到写锁后,其他获取读锁的线程将会被阻塞;当一个goroutine获取到读锁后,另外一个goroutine获取写锁将会被阻塞,如果这时候其他goroutine获取读锁,也会被阻塞

package main
import (
    "fmt"
    "sync"
)
var (
    wg      sync.WaitGroup //信号量
)
var rwlock sync.RWMutex //读写锁
func main() {
    wg.Add(1)
    //1.
    rwlock.Lock()
    fmt.Println("--main goroutine get wlock---")
    //2.
    go func() {
        rwlock.RLock() //2.1
        fmt.Println("--new goroutine get rlock---")
        rwlock.RUnlock() //2.2
        wg.Done() //2.3
    }()
    wg.Wait()       //2.4
    rwlock.Unlock() //2.5
}
  • 如上代码我们修改上面的例子让代码1获取写锁,然后代码2.5释放写锁,新goroutine内还是先获取读锁然后释放读锁。运行上面代码大家猜会输出什么?

  • 首先main所在goroutine获取了写锁,然后执行代码2.4等待新goroutine运行完毕后,释放写锁。

  • 新goroutine则是先尝试获取读锁,由于读写是互斥锁,而现在写锁已经被main所在goroutine持有了,所以新goroutine会阻塞到获取读锁的地方,而main所在goroutine会阻塞到代码2.4,这时候就达到了循环等待的条件,两个goroutine就陷入了死锁状态。运行上面代码会输出:

fatal error: all goroutines are asleep - deadlock!
--main goroutine get wlock---
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x117ffd0)
    /usr/local/go/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0x117ffc8)
    /usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:29 +0xd4
goroutine 5 [semacquire]:
sync.runtime_SemacquireMutex(0x118001c, 0x0)
    /usr/local/go/src/runtime/sema.go:71 +0x3d
sync.(*RWMutex).RLock(0x1180010)
    /usr/local/go/src/sync/rwmutex.go:50 +0x4e
main.main.func1()
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:22 +0x31
created by main.main
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:21 +0xc4

上面例子我们验证了本节提的第一个问题,下面看第二个:

package main
import (
    "fmt"
    "sync"
    "time"
)
var (
    wg      sync.WaitGroup //信号量
)
var rwlock sync.RWMutex //读写锁
func main() {
    wg.Add(1)
    rwlock.RLock() //1
    //2
    go func() {
        fmt.Println("--new goroutine1 try get wlock---")
        rwlock.Lock() //2.1
        fmt.Println("--new goroutine1  got wlock---")
        rwlock.Unlock() //2.2
        fmt.Println("--new goroutine1  release wlock---")
    }()
    time.Sleep(3 * time.Second) //3
    //4
    go func() {
        rwlock.RLock() //4.1
        fmt.Println("--new goroutine2 get rlock---")
        rwlock.RUnlock() //4.2
        wg.Done() //4.3
    }()
    wg.Wait()        //5
    rwlock.RUnlock() //6
}

运行上面代码输出:

--new goroutine1 try get wlock---
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x1180fd0)
    /usr/local/go/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0x1180fc8)
    /usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:39 +0x98
goroutine 5 [semacquire]:
sync.runtime_SemacquireMutex(0x1181018, 0x0)
    /usr/local/go/src/runtime/sema.go:71 +0x3d
sync.(*RWMutex).Lock(0x1181010)
    /usr/local/go/src/sync/rwmutex.go:98 +0x74
main.main.func1()
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:22 +0xaa
created by main.main
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:20 +0x62
goroutine 7 [semacquire]:
sync.runtime_SemacquireMutex(0x118101c, 0x0)
    /usr/local/go/src/runtime/sema.go:71 +0x3d
sync.(*RWMutex).RLock(0x1181010)
    /usr/local/go/src/sync/rwmutex.go:50 +0x4e
main.main.func2()
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:32 +0x31
created by main.main
    /Users/luxu.zlx/workspace/learn/go/workspace/lock/src/main/rwlock.go:31 +0x88
  • 代码1main goroutine获取了读锁,然后代码2创建了goroutine1尝试获取写锁,代码3让main goroutine休眠3s以便让goroutine1阻塞到获取写锁后在继续向下运行,然后代码4创建了goroutine2尝试获取读锁,假设获取成功后则会释放读锁然后信号量减去1,然后代码5就会返回,但是这里运行结果出现了死锁说明代码4.1获取读锁阻塞。

3.3 读锁是可重入锁

读锁是可重入锁,同一个gorotine可以多次获取读锁

package main
import (
    "fmt"
)
var rwlock sync.RWMutex //读写锁
func main() {
    rwlock.RLock() //1
    rwlock.RLock() //
    fmt.Printf("i got read lock twice")
    rwlock.RUnlock() //2
    rwlock.RUnlock()
}
  • 如上代码main所在goroutine先两次获取读锁,然后打印输出,然后两次释放读锁,运行上面代码可以正常打印出i got read lock twice说明读锁是可重入锁。

使用下面代码可以验证同一个线程的读锁,不能晋升为写锁:

    lock.RLock()
    lock.Lock()
    fmt.Println("test")
    lock.Unlock()
    lock.RUnlock()

上面代码执行会报错:同理当一个线程获取到了写锁后尝试获取读锁也会造成deadlock错误

四、总结

本节我们介绍了sync包中的读写锁,读写锁相比互斥锁来说,锁的粒度有所减少,这是因为读写锁,可以让多个读取共享资源的goroutine同时获取读锁,而对互斥锁来说即使是多个读取共享资源的goroutine也只有一个可以获取读锁,其他的都会被阻塞。

假期在家无聊?那就免费学习下Go语言吧!!!

Go并发编程-并发与并行

Go并发编程-并发编程难在哪里

Go并发编程-线程模型

Go并发编程-内存模型

Go并发编程-goroutine轻量级的线程

Go并发编程-runtime包

Go并发编程-互斥锁

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_37055174/article/details/104191218
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-25 01:17:06
  • 阅读 ( 1401 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢