golang底层 sync

sync.Cond

type Cond struct {
    L Locker
    notify  notifyList
}

func NewCond(l Locker) *Cond {
    return &Cond{L: l}
}

func (c *Cond) Wait() {
    t := runtime_notifyListAdd(&c.notify)    // t是一个序号
    c.L.Unlock()                        // 先解锁
    runtime_notifyListWait(&c.notify, t)        // 挂起当前 goroutine,等待 t
    c.L.Lock()                        // 恢复执行时,再加锁
}

func (c *Cond) Signal() {                // 不要求调用方一定要持有c.L
    runtime_notifyListNotifyOne(&c.notify)
}

func (c *Cond) Broadcast() {            // 不要求调用方一定要持有c.L
    runtime_notifyListNotifyAll(&c.notify)
}

type notifyList struct {    // 本质上是个队列
    wait         uint32    // 下一个 waiter 的 ticket 编号,原子自增
    notify     uint32    // 下一个被通知的 waiter 的 ticket 编号
    lock         mutex
    head     *sudog    // waiter 队列
    tail         *sudog
}

 

 

runtime_notifyListAdd wait加1并返回旧的wait值,

当前goroutine调用runtime_notifyListWait,传入wait的旧值,然后将当前g包装成sudog加入到队列里,挂起自己,等待通知

notifyListNotifyOne 会从notifyList队列里取出一个g放到等待队列里

notifyListNotifyAll 会从notifyList队列里取出所有的g放到等待队列里

 

 

sync.Mutex

    type Mutex struct {
        state int32      // 表示 mutex 锁当前的状态
        sema  uint32     // 信号量,用于唤醒 goroutine
    }
    const (            // 状态
        mutexLocked = 1 << iota
        mutexWoken
        mutexStarving
        mutexWaiterShift = iota
        starvationThresholdNs = 1e6
    )

 

Mutex 可能处于两种不同的模式:正常模式和饥饿模式。

在正常模式中,等待者按照 FIFO 的顺序排队获取锁,但是一个被唤醒的等待者有时候并不能获取 mutex, 它还需要和新到来的 goroutine 们竞争 mutex 的使用权。 如果一个 goroutine 等待 mutex 释放的时间超过 1ms,它就会将 mutex 切换到饥饿模式

在饥饿模式中,mutex 的所有权直接从解锁的 goroutine 递交到等待队列中排在最前方的 goroutine。 新到达的 goroutine 不会尝试获取 mutex,而是排到等待队列的尾部。

如果一个等待者获得了 mutex,并且它是等待队列中的最后一个,或者它等待的时间少于 1ms,它便将 mutex 切换到正常模式

正常模式下的性能会更好,因为一个 goroutine 能在即使有很多阻塞的等待者时多次连续的获得一个 mutex,饥饿模式的重要性则在于避免了病态情况下的尾部延迟。

 

Lock():

无冲突,通过 atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) 操作把当前状态设置为加锁状态

有冲突且为正常模式,自旋一段时间,如果其他 goroutine 在这段时间内释放该锁,直接获得该锁。超过了这段时间,通过调用 runtime_SemacquireMutex goroutine 进入等待状态

饥饿模式不会自旋,而是进入队列等待

 

Unlock()

修改锁的状态,调用runtime_Semrelease 唤醒等待goroutine

 

 

sync.Once

type Once struct {
    m    Mutex
    done uint32
}
func (o *Once) Do(f func()) {
   // if atomic.CompareAndSwapUint32(&o.done, 0, 1) {     f() }
   // 上面这样是错的,Do()保证返回时,f已经执行结束了,
   // 而CompareAndSwapUint32失败后Do会立即返回,此时f可能还没执行
   // 所以对o.done的赋值,要写在defer里,
   // 即使 f 触发了panic,未来也不会再次执行
   if atomic.LoadUint32(&o.done) == 0 {
      o.doSlow(f)
   }
}
func (o *Once) doSlow(f func()) {
   o.m.Lock()
   defer o.m.Unlock()
   if o.done == 0 {
      defer atomic.StoreUint32(&o.done, 1)
      f()
   }
}

 

 

sync.WaitGroup

type WaitGroup struct { state1 [3]uint32 }    // 信号量、计数器、等待数
func (wg *WaitGroup) Done() { wg.Add(-1) }
func (wg *WaitGroup) Add(delta int) {
    statep, semap := wg.state()    // 获取状态指针和信号量
    state := atomic.AddUint64(statep, uint64(delta)<<32)    // 将 delta加到计数器上
    v := int32(state >> 32)        // 计数器的值
    w := uint32(state)        // 等待数
    if v < 0 { panic("sync: negative WaitGroup counter") }    // 计数器为负 panic
    if v > 0 || w == 0 { return }    // 计数器 > 0 或者等待数为 0,直接返回
    // 计数器等于0, 等待数w > 0
    for ; w != 0; w-- { runtime_Semrelease(semap, false, 0) }
}

func (wg *WaitGroup) Wait() {        // Wait 会保持阻塞直到 WaitGroup 计数器归零
    statep, semap := wg.state()    // 获得计数器和信号量
    for {        // 一个简单的死循环,只有当计数器归零才会结束
        state := atomic.LoadUint64(statep)    // 原子读
        v := int32(state >> 32)            // 计数器
        w := uint32(state)            // 等待数
        if v == 0 { return }    // 计数器为零,退出循环
        // 等待数加 1
        if atomic.CompareAndSwapUint64(statep, state, state+1) {.....
            runtime_Semacquire(semap)    // 会阻塞到信号量为 0
            return
        }
    }
}

 

 

 

sync.Map

不太确定

 

sync.Map 类似 map[interface{}]interface{},是并发安全的。

一般代码应该用普通的map,sync.Map是为了以下两种情景:

1,key只会被写入一次,但会被读取多次,例如不断增长的cache

2,多个goroutine分别操作不同的key

 

type Map struct {
    mu     Mutex
    read     atomic.Value
    dirty     map[interface{}]*entry
    misses     int
}
type readOnly struct {
    m               map[interface{}]*entry
    amended     bool
}

 

 

read有的key,dirty中都有,且对应的value是同一个entry对象,在read中修改,dirty中能直接看到

添加新元素只会添加到dirty中,

获取元素会先到read中,read没有再到dirty中获取,去dirty中读的次数多了,就会把dirty同步到read

read的Load操作不用加锁,Store需要加锁,可以在不持有mu的情况下更新read中的entry,但更新被标记为删除的entry,需要持有mu

dirty的操作需要加锁

 

Load,Store 和 delete 通过延迟操作来均摊成本,可在常数时间内返回

Store(key, value)

1,read中有key,且未被标记为删除,则更新key对应的value,不用加锁

2,read中有key,key被标记为删除,则去掉删除标记,

dirty[key]指向read[key]所指向的底层对象,并把read[key]赋值为value

3,read中没有key,dirty里有key,直接更新dirty[key]

4,read和dirty中都没有key,把read标记为amended,并把dirty[key]赋值为value

 

Lord(key)

1,read中有key,直接返回

2,read中没有key,且read有amended标记,到dirty里找,

并将misses加1,misses大于len(dirty map)时,dirty将被提升为read,去掉amended标记

map 的下一次 store 将生成新的 dirty,包含read的全部未删除元素

3,read和dirty中都没有key,返回nil

 

delete(key)

1,read里有key,则标记为删除

2,read里没有key,dirty里有key,直接从dirty里删掉

 

posted @ 2020-05-27 22:34  是的哟  阅读(325)  评论(0编辑  收藏  举报