sync包下的mutex理解

boscoyoung · · 2274 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

Go中`sync`包下有2种`mutex`实现: * `sync.Mutex` * `sync.RWMutex` `Mutex`底层基于`sync/atomic`实现了 [Compare and Swap](https://en.wikipedia.org/wiki/Compare-and-swap). 由于该算法逻辑只需要一条汇编就可以实现,在单核CPU上运行是可以保证原子性的,但多 核CPU上运行时,需要加上`LOCK`前缀来对总线加锁,从而保证了该指令的原子性: ``` // src/sync/atomic/asm_amd64.s#L35 TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0-17 JMP ·CompareAndSwapUint32(SB) TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-17 // 初始化参数 MOVQ addr+0(FP), BP MOVL old+8(FP), AX MOVL new+12(FP), CX // 锁总线 LOCK // 执行Compare and Exchange CMPXCHGL CX, 0(BP) // 处理返回值 SETEQ swapped+16(FP) RET ``` `sync.Mutex`实现了`sync.Locker`接口,主要有`Lock()/Unlock()`2个method,值得注意 的是,不能重复的对已经解锁的mutex解锁,否则会`panic`. `sync.Mutex`阻塞进程的方式其实是让进程不断的轮询. 值得注意的是`Mutex`的`zero value`是一个`unlocked mutex`: ``` // A Mutex is a mutual exclusion lock. // Mutexes can be created as part of other structures; // the zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. ``` 在加锁和解锁的时候,都会检查是否`enabled race`,允许抢占可能会造成不可预知的问题 看起来只有在`sync.RWMutex`和`sync.WaitGroup`里面才使用了`race.Enable()`,目前 还不清楚这个东西的作用是什么. Go1.6中引入了更可靠的竞争检测机制, [Introducing the Go Race Detector](https://blog.golang.org/race-detector). 只要执行`Go Command`的时候带上`-race`即可: 例 1 [e4_1.go](https://github.com/yangyuqian/technical-articles/blob/master/go/examples/src/concurrency/e4_1.go) 在没有加锁的情况下存在多个`goroutine`同时读写公有数据 ``` go run -race e4_1.go ... Found 2 data race(s) ``` 另外, `sync.RWMutex`,同样实现了`sync.Locker`接口,提供了更灵活的锁机制: ``` // An RWMutex is a reader/writer mutual exclusion lock. // The lock can be held by an arbitrary number of readers or a single writer. // RWMutexes can be created as part of other structures; // the zero value for a RWMutex is an unlocked mutex. // // An RWMutex must not be copied after first use. // // If a goroutine holds a RWMutex for reading, it must not expect this or any // other goroutine to be able to also take the read lock until the first read // lock is released. In particular, this prohibits recursive read locking. // This is to ensure that the lock eventually becomes available; // a blocked Lock call excludes new readers from acquiring the lock. ``` 有意思的是,`sync.RWMutex`会检测公有数据的修改; 例 2 [e4_2.go](https://github.com/yangyuqian/technical-articles/blob/master/go/examples/src/concurrency/e4_2.go)中模拟了2组`goroutine`,一组专门读数据,一组专门 写数据;其中读数据的速度非常快,写数据会比较慢(加了随机延迟): ``` package main import ( "math/rand" "sync" "time" ) var shared = struct { *sync.RWMutex count int }{} var wg *sync.WaitGroup const N = 10 func main() { rand.Seed(time.Now().Unix()) shared.RWMutex = new(sync.RWMutex) wg = new(sync.WaitGroup) wg.Add(2 * N) defer wg.Wait() for i := 0; i < N; i++ { // write goroutines go func(ii int) { shared.Lock() duration := rand.Intn(5) // shared.Lock() time.Sleep(time.Duration(duration) * time.Second) shared.count++ println(ii, "write --- shared.count =>", shared.count) // shared.Unlock() shared.Unlock() wg.Done() }(i) // read goroutines go func(ii int) { shared.RLock() println(ii, "read --- shared.count =>", shared.count) shared.RUnlock() wg.Done() }(i) } } ``` 读写锁的效果就是,在写锁锁定的时候,会阻塞所有的读锁, 例4.2里面读操作非常快,所以第一次写操作完成之后, 所有的读操作就一次完成了,后面的就只有读操作了 例 3 [e4_3.go](https://github.com/yangyuqian/technical-articles/blob/master/go/examples/src/concurrency/e4_3.go) 去掉了公有数据的读写操作, 模拟了例4.2里面的延迟和锁;本来以为锁会检查逻辑里面的数据修改,发现并不是; 看起来写锁对读锁的阻塞是全局的,只要一个进程内的写锁就会阻塞所有的读锁; 从底层实现上来看,上面的判断应该是准确的: ``` // src/sync/rwmutex.go#L35 // RLock locks rw for reading. func (rw *RWMutex) RLock() { if race.Enabled { _ = rw.w.state race.Disable() } if atomic.AddInt32(&rw.readerCount, 1) < 0 { // A writer is pending, wait for it. runtime_Semacquire(&rw.readerSem) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) } } ``` 操作系统只对`goroutine`实际使用的系统线程分配资源,而且`goroutine`实现了进程中 上线文切换机制,比操作系统级切换系统线程上下文高效;尽管这样,并发过高的情况下 `goroutine`之间上下文切换也会对程序性能带来比较大的影响;

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2274 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传