Go-同步原语与锁互斥锁与读写锁

Posted 链人成长chainerup

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go-同步原语与锁互斥锁与读写锁相关的知识,希望对你有一定的参考价值。


本文将讲解一下Go语言中的同步原语与锁。会阐述几种常见的锁,剖析其流程,然后针对每种同步原语举几个例子。由于文章比较长,为方便阅读,将这部分拆解为两部分。本文是第一部分 互斥锁与读写锁。

环境:go version go1.8.7 darwin/amd64

1 mutex

1.1 结构

先看下mutex在底层的结构:代码位置sync/mutex.go

 
   
   
 
  1. type Mutex struct {

  2. state int32 // 状态

  3. sema uint32 // 信号

  4. }

在mutex中 主要有mutexLocked , mutexWorken 两种状态,mutexWaiterShift 是统计了等待mutex的routine 数量。

1.2 流程

参考文献中讲到Go1.9 针对互斥锁增加了饥饿模式,我们这儿不讨论了哈。

1.2.1 Lock

流程大体是这样子的:

1.2.2 Unlock

Go-同步原语与锁(一)互斥锁与读写锁

1.3 例子

 
   
   
 
  1. package main


  2. import (

  3. "fmt"

  4. "math/rand"

  5. "sync"

  6. "testing"

  7. "time"

  8. )


  9. func TestMutex(t *testing.T) {

  10. lock := sync.Mutex{}

  11. var a map[int]int

  12. a = make(map[int]int, 5)


  13. a[8] = 10

  14. a[3] = 10

  15. a[2] = 10

  16. a[1] = 10

  17. a[18] = 10


  18. for i := 0; i < 2; i++ {

  19. go func(b map[int]int) {

  20. lock.Lock()

  21. b[8] = rand.Intn(100)

  22. lock.Unlock()

  23. }(a)

  24. }

  25. lock.Lock()

  26. fmt.Println("main routine print in mutex......")

  27. fmt.Println(a)

  28. lock.Unlock()


  29. time.Sleep(time.Second)

  30. fmt.Println("main routine print at end......")

  31. fmt.Println(a)

  32. }

结论是:

 
   
   
 
  1. main routine print in mutex......

  2. map[8:10 3:10 2:10 1:10 18:10]

  3. main routine print at end......

  4. map[2:10 1:10 18:10 8:87 3:10]

2 RWmutex

RWmutex 读写锁,对共享资源的”读操作“ 和”写操作“ 进行了区分,更加细化。其基本的原则是:

对某个受到读写锁保护的共享资源,多个写不能同时操作,读写也不可以同时操作,但是多个读可以同时进行。

2.1 结构

 
   
   
 
  1. type RWMutex struct {

  2. w Mutex // 写操作之间的互斥锁

  3. writerSem uint32 // semaphore for writers to wait for completing readers

  4. readerSem uint32 // semaphore for readers to wait for completing writers

  5. readerCount int32 // 正在进行的读操作的数量

  6. readerWait int32 // 当写操作被阻塞时等待的读操作个数

  7. }

每个字段的含义在注释里面哈~

2.2 流程

这部分我们只做简单的流程分析。

2.2.1 读锁的获取
2.2.2 读锁的释放

2.2.3 写锁的获取
  • 此时有读锁:进入休眠,等待被唤醒

  • 此时有写锁:互斥,进入等待状态

  • 此时没有读锁、写锁:获取写锁

2.2.3 写锁的释放

先释放写锁,然后看有没有在等待中的读操作:如果有读操作在等待,先让读锁执行;如果没有读操作,就释放写锁,让其他写锁获取当前锁。这样操作是防止读操作被饿死。

2.3 例子

先来个”读-读“的例子:

 
   
   
 
  1. func TestRWMutex_multiRead(t *testing.T) {

  2. m = new(sync.RWMutex)


  3. // 多个同时读

  4. now := time.Now().Nanosecond()

  5. go read(1, now)

  6. now2 := time.Now().Nanosecond()

  7. go read(2,now2)


  8. time.Sleep(2*time.Second)

  9. }

  10. func read(i int, beginTime int) {

  11. println(time.Now().Nanosecond() - beginTime, i,"read start")


  12. m.RLock()

  13. println(time.Now().Nanosecond() - beginTime, i,"reading")

  14. time.Sleep(1*time.Second)

  15. println(time.Now().Nanosecond() - beginTime, i,"read over")

  16. m.RUnlock()

  17. }

结论是:

 
   
   
 
  1. 16000 1 read start

  2. 27000 1 reading

  3. 28000 2 read start

  4. 38000 2 reading

  5. 3159000 2 read over

  6. 3167000 1 read over

我们看到,reader1没有结束,reader2就开始读了,验证了我们的读-读是可以并存的。

再来个”读-写混合“的例子:

 
   
   
 
  1. func TestRWMutex_WriteRead(t *testing.T) {

  2. m = new(sync.RWMutex)

  3. beginTime := time.Now().Nanosecond()

  4. // 有读有写: 读-写-写-读

  5. go read(1, beginTime)

  6. go write(2, beginTime)

  7. go write(3, beginTime)

  8. go read(4, beginTime)


  9. time.Sleep(5*time.Second)

  10. }


  11. func read(i int, beginTime int) {

  12. println(time.Now().Nanosecond() - beginTime, i,"read start")


  13. m.RLock()

  14. println(time.Now().Nanosecond() - beginTime, i,"reading")

  15. time.Sleep(1*time.Second)

  16. println(time.Now().Nanosecond() - beginTime, i,"read over")

  17. m.RUnlock()

  18. }


  19. func write(i int, beginTime int) {

  20. println(time.Now().Nanosecond() - beginTime, i,"write start")


  21. m.Lock()

  22. println(time.Now().Nanosecond() - beginTime, i,"writing")

  23. time.Sleep(1*time.Second)

  24. println(time.Now().Nanosecond() - beginTime, i,"write over")

  25. m.Unlock()


  26. }

看下执行某一次的结果:

 
   
   
 
  1. 10000 2 write start

  2. 24000 2 writing

  3. 42000 4 read start

  4. 44000 1 read start

  5. 20000 3 write start

  6. 1926000 2 write over

  7. 1955000 4 reading

  8. 1968000 1 reading

  9. 5641000 1 read over

  10. 5648000 4 read over

  11. 5714000 3 writing

  12. 10094000 3 write over

我们看到 先执行了写操作2,等到写操作2执行完毕之后,读操作41才开始执行,等这俩读都执行完,最后才执行了写操作3

3 小结

本文是Go-同步原语与锁的第一部分:互斥锁与读写锁。从结构、获取释放锁、例子三个维度进行了讲解。

4 参考文献

同步原语与锁 https://draveness.me/golang/concurrency/golang-sync-primitives.htmlGo 1.8 源码

5 其他

以上是关于Go-同步原语与锁互斥锁与读写锁的主要内容,如果未能解决你的问题,请参考以下文章

GO语言并发编程之互斥锁读写锁详解

java中ReentrantReadWriteLock读写锁的使用

Python的互斥锁与信号量

ReentrantReadWriteLock场景应用

Go同步等待组/互斥锁/读写锁

再探 同步与互斥