sync包下的mutex理解

boscoyoung · 2016-11-20 08:54:29 · 2411 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2016-11-20 08:54:29 的主题,其中的信息可能已经有所发展或是发生改变。

Go中sync包下有2种mutex实现:

  • sync.Mutex

  • sync.RWMutex

Mutex底层基于sync/atomic实现了 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阻塞进程的方式其实是让进程不断的轮询.

值得注意的是Mutexzero 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.RWMutexsync.WaitGroup里面才使用了race.Enable(),目前 还不清楚这个东西的作用是什么.

Go1.6中引入了更可靠的竞争检测机制, Introducing the Go Race Detector. 只要执行Go Command的时候带上-race即可:

例 1 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中模拟了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 去掉了公有数据的读写操作, 模拟了例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

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