golang 源码分析之sync

sync 同步
sync.Mutex
sync.RWMutex
sync.WaitGroup
sync.Once
sync.Cond
Mutex
// A Mutex must not be copied after first use.
type Mutex struct {
	state int32
	sema  uint32
}

mutex.Lock()

func (m *Mutex) Lock() {
	// 先看看能不能直接获取到
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		// 能就直接返回
		return
	}
	// 不能直接获取到就调用lockSlow 慢慢获取
	m.lockSlow()
}

lockSlow()

func (m *Mutex) lockSlow() {
	var waitStartTime int64
	starving := false
	awoke := false
	iter := 0
	old := m.state
	for {
		// 不是饥饿模式下尝试通过自旋获取
		//runtime_canSpin为true的条件
		//1运行在多 CPU 的机器上
		//2当前 Goroutine 为了获取该锁进入自旋的次数小于四次
		//3当前机器上至少存在一个正在运行的处理器 P 并且处理的运行队列为空
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		// Don't try to acquire starving mutex, new arriving goroutines must queue.
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}
		// 切换为饥饿模式
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		if awoke {
			// 被唤醒
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			new &^= mutexWoken
		}
		//通过cas获取锁
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break // locked the mutex with CAS
			}
			// 如果之前已经在等待,就在队列的最前面等待.
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			// 休眠
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			if old&mutexStarving != 0 {
				// 如果被唤醒了并且互斥锁处于饥饿状态,那么我们就是饥饿模式下的管理者
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
					// 退出饥饿模式
					delta -= mutexStarving
				}
				atomic.AddInt32(&m.state, delta)
				break
			}
			awoke = true
			iter = 0
		} else {
			old = m.state
		}
	}
	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
}
如果互斥锁处于初始化状态,就会直接通过置位 mutexLocked 加锁;

如果互斥锁处于 mutexLocked 并且在普通模式下工作,就会进入自旋,执行 30 次 PAUSE 指令消耗 CPU 时间等待锁的释放;

如果当前 Goroutine 等待锁的时间超过了 1ms,互斥锁就会切换到饥饿模式;

互斥锁在正常情况下会通过 sync.runtime_SemacquireMutex 函数将尝试获取锁的 Goroutine 切换至休眠状态,等待锁的持有者唤醒当前 Goroutine;

如果当前 Goroutine 是互斥锁上的最后一个等待的协程或者等待的时间小于 1ms,当前 Goroutine 会将互斥锁切换回正常模式;

mutex.UnLock()

func (m *Mutex) Unlock() {
	//如果该函数返回的新状态等于 0,当前 Goroutine 就成功解锁了互斥锁;
    //如果该函数返回的新状态不等于 0,这段代码会调用 sync.Mutex.unlockSlow 方法开始慢速解锁:
	if race.Enabled {
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}
	// 快速解锁
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
		//在正常模式下如果互斥锁不存在等待者或者互斥锁的 mutexLocked、mutexStarving、mutexWoken 状态不都为 0,那么当前方法就可以直接返回,不需要唤醒其他等待者;
		//在正常模式下如果互斥锁存在等待者,会通过 sync.runtime_Semrelease 唤醒等待者并移交锁的所有权;
		//在饥饿模式下,代码会直接调用 sync.runtime_Semrelease 方法将当前锁交给下一个正在尝试获取锁的等待者,等待者被唤醒后会得到锁,在这时互斥锁还不会退出饥饿状态;
		m.unlockSlow(new)
	}
}
RWMutex
type RWMutex struct {
	w           Mutex   // 复用mutex
	writerSem   uint32  // 写等待读
	readerSem   uint32  // 读等待写
	readerCount int32   // 正在执行的读操作个数
	readerWait  int32   // 写操作阻塞了之后读操作的等待个数
}

RWMutex.Lock()

func (rw *RWMutex) Lock() {
	// 锁上 只能一个写进入
	rw.w.Lock()
	// 标记 有写在等待
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// 等待之前的读操作执行完
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
}

RWMutex.UnLock()

func (rw *RWMutex) Unlock() {
	// 可以执行读操作了
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}
	// 通过 for 循环触发所有由于获取读锁而陷入等待的 Goroutine:
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// 释放锁
	rw.w.Unlock()
}

RWMutex.RLock()

func (rw *RWMutex) RLock() {
	//如果该方法返回负数 — 其他 Goroutine 获得了写锁,当前 Goroutine 就会调用 sync.runtime_SemacquireMutex 陷入休眠等待锁的释放;
        //如果该方法的结果为非负数 — 没有 Goroutine 获得写锁,当前方法就会成功返回;
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// A writer is pending, wait for it.
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
}

RWMutex.RUnlock()

func (rw *RWMutex) RUnlock() {
    //如果返回值大于等于零 — 读锁直接解锁成功;
    //如果返回值小于零 — 有一个正在执行的写操作,在这时会调用sync.RWMutex.rUnlockSlow 方法;
    //sync.RWMutex.rUnlockSlow 会减少获取锁的写操作等待的读操作数 readerWait 并在所有读操作都被释放之后触发写操作的信号量 writerSem,该信号量被触发时,调度器就会唤醒尝试获取写锁的 Goroutine。
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)
	}
}
WaitGroup
type WaitGroup struct {
	// 不能通过再拷贝的方式赋值
	noCopy noCopy
	//状态和信号量
	state1 [3]uint32
}

WaitGroup.Add()

func (wg *WaitGroup) Add(delta int) {
	// 获取状态和信号量,因为state1 在32和64系统里代表不一样的,所以有个方法来获取状态和信号量
	statep, semap := wg.state()
	// 增加
	state := atomic.AddUint64(statep, uint64(delta)<<32)
	v := int32(state >> 32)
	w := uint32(state)
	// 总数小于0 panic
	if v < 0 {
		panic("sync: negative WaitGroup counter")
	}
	// 
	if w != 0 && delta > 0 && v == int32(delta) {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	if v > 0 || w == 0 {
		return
	}
	if *statep != state {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	// Reset waiters count to 0.
	*statep = 0
	for ; w != 0; w-- {
		// 唤醒
		runtime_Semrelease(semap, false, 0)
	}
}

WaitGroup.Wait()

// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
	// 获取状态和信号量
	statep, semap := wg.state()
	// 
	for {
		// 查看当前的数量
		state := atomic.LoadUint64(statep)
		v := int32(state >> 32)
		w := uint32(state)
		// 为0直接返回
		if v == 0 {
			// Counter is 0, no need to wait.
			return
		}
		// 记录等待
		if atomic.CompareAndSwapUint64(statep, state, state+1) {
			// 挂起 等调用add方法到0唤醒
			runtime_Semacquire(semap)
			if *statep != 0 {
				panic("sync: WaitGroup is reused before previous Wait has returned")
			}
			if race.Enabled {
				race.Enable()
				race.Acquire(unsafe.Pointer(wg))
			}
			return
		}
	}
}
Once

保证在 Go 程序运行期间的某段代码只会执行一次。

type Once struct {
	// 标记是否已经执行
	done uint32
	m    Mutex
}

Once.Do()

func (o *Once) Do(f func()) {
	// 如果done为0
	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		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()
	}
}
Once.Cond()

让一系列的 Goroutine 都在满足特定条件时被唤醒。

type Cond struct {
	noCopy  noCopy  // 编译器不允许拷贝
	L       Locker
	notify  notifyList
	checker copyChecker //运行期不允许拷贝
}
type notifyList struct {
	wait   uint32           //正在等待的
	notify uint32           //已经通知的
	lock   uintptr // key field of the mutex
	head   unsafe.Pointer   //链表头
	tail   unsafe.Pointer   //链表尾
}

Cond.Wait()

// wait方法一看 调用wait前要加锁,wait后要解锁,不然就panic啦
func (c *Cond) Wait() {
	c.checker.check()
	t := runtime_notifyListAdd(&c.notify)   // 等待+1
	c.L.Unlock()                        // 解锁
	runtime_notifyListWait(&c.notify, t)    //挂起等待
	c.L.Lock() //加锁
}

Cond.Signal() 唤醒一个在wait中的(队头)

func (c *Cond) Signal() {
	c.checker.check()
	runtime_notifyListNotifyOne(&c.notify)
}
// 找到一个满足条件的唤醒
func notifyListNotifyOne(l *notifyList) {
	t := l.notify
	atomic.Store(&l.notify, t+1)
	for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next {
		if s.ticket == t {
			n := s.next
			if p != nil {
				p.next = n
			} else {
				l.head = n
			}
			if n == nil {
				l.tail = p
			}
			s.next = nil
			readyWithTime(s, 4)
			return
		}
	}
}

Cond.Broadcast() 唤醒全部

func (c *Cond) Broadcast() {
	c.checker.check()
	runtime_notifyListNotifyAll(&c.notify)
}
// 唤醒全部
func notifyListNotifyAll(l *notifyList) {
	s := l.head
	l.head = nil
	l.tail = nil

	atomic.Store(&l.notify, atomic.Load(&l.wait))

	for s != nil {
		next := s.next
		s.next = nil
		readyWithTime(s, 4)
		s = next
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值