// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sync provides basic synchronization primitives such as mutual
// exclusion locks. Other than the Once and WaitGroup types, most are intended
// for use by low-level library routines. Higher-level synchronization is
// better done via channels and communication.
// 包同步提供基本的同步原语,例如互斥锁。除了Once和WaitGroup类型之外,大多数类型都用于低级库例程。
// 通过信道和通信可以更好地实现更高级别的同步。
// Values containing the types defined in this package should not be copied.
// 不应复制包含此包中定义的类型的值。
package sync
import (
"internal/race"
"sync/atomic"
"unsafe"
)
// Provided by runtime via linkname.
// 由运行时通过链接名称提供。
func throw(string)
func fatal(string)
// A Mutex is a mutual exclusion lock.
// Mutex是一种互斥锁。
// The zero value for a Mutex is an unlocked mutex.
// Mutex的零值是一个未锁定的互斥量。
// A Mutex must not be copied after first use.
// Mutex在首次使用后不得复制。
// In the terminology of the Go memory model,
// the n'th call to Unlock “synchronizes before” the m'th call to Lock
// for any n < m.
// 在Go内存模型的术语中,对于任何n<m,第n次调用Unlock“同步于”第m次调用Lock。
// A successful call to TryLock is equivalent to a call to Lock.
// A failed call to TryLock does not establish any “synchronizes before”
// relation at all.
// 对TryLock的成功调用相当于对Lock的调用。对TryLock的失败调用根本不会建立任何“同步之前”关系。
type Mutex struct {
state int32 // 表示锁的状态:waiter、starving、woken、locked
sema uint32 // 表示信号量
}
// A Locker represents an object that can be locked and unlocked.
// Locker表示可以锁定和解锁的对象。
type Locker interface {
Lock()
Unlock()
}
const (
mutexLocked = 1 << iota // mutex is locked 互斥被锁定
mutexWoken // 表示是否有协程已被唤醒,0-没有被唤醒协程 1-有被唤醒协程
mutexStarving // 表示该Mutex是否处于饥饿状态,0-没有饥饿,1-饥饿
mutexWaiterShift = iota // 表示阻塞等待锁的个数,协程解锁时根据次值判断是否需要释放信号量
// Mutex fairness.
// Mutex公平。
// Mutex can be in 2 modes of operations: normal and starvation.
// In normal mode waiters are queued in FIFO order, but a woken up waiter
// does not own the mutex and competes with new arriving goroutines over
// the ownership. New arriving goroutines have an advantage -- they are
// already running on CPU and there can be lots of them, so a woken up
// waiter has good chances of losing. In such case it is queued at front
// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
// it switches mutex to the starvation mode.
// 互斥可以有两种操作模式:正常和饥饿。在正常模式下,服务者按先进先出顺序排队,但被唤醒的服务者不拥有互斥,并与新到达的goroutine争夺所有权。
// 新来的goroutine有一个优势——它们已经在CPU上运行了,而且可能有很多,所以醒来的服务者很有可能会输。
// 在这种情况下,它被排在等待队列的前面。如果服务程序在超过1ms的时间内未能获取互斥对象,则会将互斥对象切换到饥饿模式。
// In starvation mode ownership of the mutex is directly handed off from
// the unlocking goroutine to the waiter at the front of the queue.
// New arriving goroutines don't try to acquire the mutex even if it appears
// to be unlocked, and don't try to spin. Instead they queue themselves at
// the tail of the wait queue.
// 在饥饿模式下,互斥体的所有权直接从解锁goroutine移交给队列前面的服务者。
// 新到达的goroutine不会试图获取互斥体,即使它看起来已经解锁,也不会试图旋转。相反,他们把自己排在等待队列的尾部。
// If a waiter receives ownership of the mutex and sees that either
// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
// it switches mutex back to normal operation mode.
// 如果服务对象接收到互斥对象的所有权,并且发现(1)它是队列中的最后一个服务对象,或者(2)它等待的时间小于1ms,则将互斥对象切换回正常操作模式。
// Normal mode has considerably better performance as a goroutine can acquire
// a mutex several times in a row even if there are blocked waiters.
// Starvation mode is important to prevent pathological cases of tail latency.
// 普通模式的性能要好得多,因为goroutine可以连续多次获取互斥对象,即使存在阻塞的等待程序。饥饿模式对于预防尾部潜伏的病理性病例很重要。
starvationThresholdNs = 1e6
)
// Lock locks m. 锁定锁m。
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
// 如果锁已经在使用中,则调用goroutine会阻塞,直到互斥对象可用。
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
// 快速路径:获取未锁定的互斥。
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// Slow path (outlined so that the fast path can be inlined)
// 慢速路径(勾勒出轮廓,以便可以内联快速路径)
m.lockSlow()
}
// TryLock tries to lock m and reports whether it succeeded.
// TryLock尝试锁定m并报告是否成功。
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem
// in a particular use of mutexes.
// 请注意,虽然TryLock的正确使用确实存在,但它们很少使用,并且TryLock通常是互斥体的特定使用中更深层次问题的标志。
func (m *Mutex) TryLock() bool {
old := m.state
if old&(mutexLocked|mutexStarving) != 0 {
return false
}
// There may be a goroutine waiting for the mutex, but we are
// running now and can try to grab the mutex before that
// goroutine wakes up.
// 可能有一个goroutine在等待互斥,但我们现在正在运行,可以尝试在goroutine唤醒之前获取互斥。
if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
return false
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return true
}
func (m *Mutex) lockSlow() {
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state
for {
// Don't spin in starvation mode, ownership is handed off to waiters
// so we won't be able to acquire the mutex anyway.
// 不要在饥饿模式下旋转,所有权被交给了服务员,所以我们无论如何都无法获得互斥。
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// Active spinning makes sense.
// Try to set mutexWoken flag to inform Unlock
// to not wake other blocked goroutines.
// 主动旋转是有意义的。尝试设置mutexWoken标志来通知Unlock不要唤醒其他被阻止的goroutine。
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.
// 不要试图获取饥饿的互斥,新到达的goroutine必须排队。
if old&mutexStarving == 0 {
new |= mutexLocked
}
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift
}
// The current goroutine switches mutex to starvation mode.
// But if the mutex is currently unlocked, don't do the switch.
// Unlock expects that starving mutex has waiters, which will not
// be true in this case.
// 当前goroutine将互斥体切换到饥饿模式。但如果互斥体当前未锁定,则不要进行切换。Unlock预计饥饿的互斥体会有等待程序,但在这种情况下情况并非如此。
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
if awoke {
// The goroutine has been woken from sleep,
// so we need to reset the flag in either case.
// goroutine已经从睡眠中唤醒,所以无论哪种情况,我们都需要重置标志。
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
new &^= mutexWoken
}
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&(mutexLocked|mutexStarving) == 0 {
break // locked the mutex with CAS 使用CAS锁定互斥锁
}
// If we were already waiting before, queue at the front of the queue.
// 如果我们之前已经在等了,那就排在队伍的最前面。
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 this goroutine was woken and mutex is in starvation mode,
// ownership was handed off to us but mutex is in somewhat
// inconsistent state: mutexLocked is not set and we are still
// accounted as waiter. Fix that.
// 如果这个goroutine被唤醒,并且mutex处于饥饿模式,那么所有权就交给了我们,
// 但mutex处于某种不一致的状态:mutexLocked没有设置,我们仍然被视为服务员。解决这个问题。
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
throw("sync: inconsistent mutex state")
}
delta := int32(mutexLocked - 1<<mutexWaiterShift)
if !starving || old>>mutexWaiterShift == 1 {
// Exit starvation mode. 退出饥饿模式。
// Critical to do it here and consider wait time.
// Starvation mode is so inefficient, that two goroutines
// can go lock-step infinitely once they switch mutex
// to starvation mode.
// 关键是要在这里做到这一点,并考虑等待时间。饥饿模式是如此低效,以至于两个goroutine一旦将互斥切换到饥饿模式,就可以无限锁定。
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
break
}
awoke = true
iter = 0
} else {
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
// Unlock unlocks m. 解锁解锁m。
// It is a run-time error if m is not locked on entry to Unlock.
// 如果m在进入Unlock时没有被锁定,这是一个运行时错误。
// A locked Mutex is not associated with a particular goroutine.
// 锁定的Mutex与特定的goroutine没有关联。
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
// 允许一个goroutine锁定Mutex,然后安排另一个gorroutine解锁它。
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// Fast path: drop lock bit.
new := atomic.AddInt32(&m.state, -mutexLocked)
if new != 0 {
// Outlined slow path to allow inlining the fast path. 勾勒出慢速路径以允许内联快速路径。
// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
// 为了在追踪过程中隐藏unlockSlow,我们在追踪GoUnblock时跳过一个额外的帧。
// 如果有等待的goroutine,唤醒等待的goroutine
m.unlockSlow(new)
}
}
func (m *Mutex) unlockSlow(new int32) {
if (new+mutexLocked)&mutexLocked == 0 {
fatal("sync: unlock of unlocked mutex")
}
if new&mutexStarving == 0 {
old := new
for {
// If there are no waiters or a goroutine has already
// been woken or grabbed the lock, no need to wake anyone.
// In starvation mode ownership is directly handed off from unlocking
// goroutine to the next waiter. We are not part of this chain,
// since we did not observe mutexStarving when we unlocked the mutex above.
// So get off the way.
// 如果没有服务员,或者一个goroutine已经被叫醒或被锁了,就不需要叫醒任何人。在饥饿模式下,所有权直接从解锁goroutine转移到下一个服务员。
// 我们不是这个链的一部分,因为当我们解锁上面的互斥时,我们没有观察到互斥饥饿。所以让开。
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// Grab the right to wake someone.
// 抓住权利叫醒某人。
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime_Semrelease(&m.sema, false, 1)
return
}
old = m.state
}
} else {
// Starving mode: handoff mutex ownership to the next waiter, and yield
// our time slice so that the next waiter can start to run immediately.
// Note: mutexLocked is not set, the waiter will set it after wakeup.
// But mutex is still considered locked if mutexStarving is set,
// so new coming goroutines won't acquire it.
// 饥饿模式:将互斥的所有权移交给下一个服务员,并产生我们的时间片,以便下一个侍者可以立即开始运行。
// 注意:没有设置互斥锁定,服务员会在唤醒后设置它。但如果设置了互斥饥饿,互斥仍然被认为是锁定的,所以新的goroutine不会获取它。
runtime_Semrelease(&m.sema, true, 1)
}
}
【Golang1.20源码阅读】sync/mutex.go
原创
©著作权归作者所有:来自51CTO博客作者深漂小码哥的原创作品,请联系作者获取转载授权,否则将追究法律责任
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
Apache Doris 聚合函数源码阅读与解析|源码解读系列
Apache Doris Active Contributor 隐形通过本文记录下对源码的理解,以方便新人快速上手源码开发。
Apache Doris 数据库 大数据 数据分析 数据仓库 -
【Golang1.20源码阅读】sync/rwmutex.go
【代码】【Golang1.20源码阅读】sync/rwmutex.go。
golang 信号量 Go sed -
【Golang1.20源码阅读】sync/waitgroup.go
【代码】【Golang1.20源码阅读】sync/waitgroup.go。
golang Group sed Go -
【Golang1.20源码阅读】math/const.go
golang 数学常量极限值
golang Powered by 金山文档 ide Max 浮点型 -
【Golang1.20源码阅读】builtin/builtin.go
【Golang源码阅读】builtin/builtin.go
golang Powered by 金山文档 Go 字符串 ci -
【Golang1.20源码阅读】bytes/buffer.go
【代码】【Golang1.20源码阅读】bytes/buffer.go。
golang ci 字节数 字符串 -
【Golang1.20源码阅读】runtime/string.go
【代码】【Golang1.20源码阅读】runtime/string.go。
golang 字符串 sed 编译器 -
【Golang1.20源码阅读】strings/compare.go
【代码】【Golang1.20源码阅读】strings/compare.go。
golang 字符串 Go 比较运算符 -
【Golang1.20源码阅读】runtime/chan.go
【代码】【Golang1.20源码阅读】chan.go。
golang sed 堆栈 Go -
【Golang1.20源码阅读】context/context.go
【代码】【Golang1.20源码阅读】context/context.go。
golang 开发语言 后端 sed User -
【Golang1.20源码阅读】runtime/map.go
【代码】【Golang1.20源码阅读】runtime/map.go。
golang 开发语言 后端 Go 数组 -
【Golang1.20源码阅读】runtime/select.go
【代码】【Golang1.20源码阅读】runtime/select.go。
golang 开发语言 后端 堆栈 数组 -
【Golang1.20源码阅读】runtime/slice.go
【代码】【Golang1.20源码阅读】slice.go。
golang 开发语言 初始化 ci -
【Golang1.20源码阅读】crypto/sha256.go
【代码】【Golang1.20源码阅读】crypto/sha256.go。
golang 校验和 数据 ide