文章目录
-
- [1. 面试考情总览](#1. 面试考情总览)
-
- [1.1 高频考点(★★★★★)](#1.1 高频考点(★★★★★))
- [1.2 次高频考点(★★★★)](#1.2 次高频考点(★★★★))
- [1.3 进阶考点(★★★)](#1.3 进阶考点(★★★))
- [2. 基础理论层](#2. 基础理论层)
-
- [2.1 Goroutine基础](#2.1 Goroutine基础)
- [2.2 Channel基础](#2.2 Channel基础)
- [2.3 同步原语基础](#2.3 同步原语基础)
- [3. GMP调度器完整底层原理(★★★★★)](#3. GMP调度器完整底层原理(★★★★★))
-
- [3.1 G/M/P核心结构](#3.1 G/M/P核心结构)
-
- [3.1.1 G结构体(Goroutine)](#3.1.1 G结构体(Goroutine))
- [3.1.2 M结构体(Machine/OS线程)](#3.1.2 M结构体(Machine/OS线程))
- [3.1.3 P结构体(Processor)](#3.1.3 P结构体(Processor))
- [3.2 GMP整体架构](#3.2 GMP整体架构)
- [3.3 核心组件对比表](#3.3 核心组件对比表)
- [3.4 调度循环(schedule)](#3.4 调度循环(schedule))
- [3.5 findRunnable寻找可运行G](#3.5 findRunnable寻找可运行G)
- [3.6 工作窃取(Work Stealing)机制](#3.6 工作窃取(Work Stealing)机制)
- [3.7 Goroutine创建流程](#3.7 Goroutine创建流程)
- [3.8 抢占式调度](#3.8 抢占式调度)
-
- [3.8.1 异步抢占](#3.8.1 异步抢占)
- [3.8.2 同步安全点抢占](#3.8.2 同步安全点抢占)
- [3.9 系统调用时的P处理](#3.9 系统调用时的P处理)
- [3.10 GOMAXPROCS的作用](#3.10 GOMAXPROCS的作用)
- [4. Channel底层原理(★★★★★)](#4. Channel底层原理(★★★★★))
-
- [4.1 hchan结构体](#4.1 hchan结构体)
- [4.2 无缓冲与有缓冲Channel对比](#4.2 无缓冲与有缓冲Channel对比)
- [4.3 Channel发送流程](#4.3 Channel发送流程)
- [4.4 Channel接收流程](#4.4 Channel接收流程)
- [4.5 关闭Channel](#4.5 关闭Channel)
- [4.6 nil/closed channel操作对照表](#4.6 nil/closed channel操作对照表)
- [4.7 Channel内存泄漏场景](#4.7 Channel内存泄漏场景)
- [5. sync包同步原语底层原理(★★★★★)](#5. sync包同步原语底层原理(★★★★★))
-
- [5.1 sync.Mutex互斥锁](#5.1 sync.Mutex互斥锁)
-
- [5.1.1 Mutex两种模式对比](#5.1.1 Mutex两种模式对比)
- [5.1.2 Lock加锁流程](#5.1.2 Lock加锁流程)
- [5.1.3 Unlock解锁流程](#5.1.3 Unlock解锁流程)
- [5.1.4 Mutex加锁状态流转图](#5.1.4 Mutex加锁状态流转图)
- [5.2 sync.RWMutex读写锁](#5.2 sync.RWMutex读写锁)
-
- [5.2.1 RLock读加锁](#5.2.1 RLock读加锁)
- [5.2.2 RUnlock读解锁](#5.2.2 RUnlock读解锁)
- [5.2.3 Lock写加锁](#5.2.3 Lock写加锁)
- [5.2.4 Unlock写解锁](#5.2.4 Unlock写解锁)
- [5.2.5 RWMutex写饥饿问题](#5.2.5 RWMutex写饥饿问题)
- [5.3 sync.WaitGroup](#5.3 sync.WaitGroup)
-
- [5.3.1 Add添加计数](#5.3.1 Add添加计数)
- [5.3.2 Wait等待](#5.3.2 Wait等待)
- [5.3.3 Done完成](#5.3.3 Done完成)
- [5.4 sync.Once确保只执行一次](#5.4 sync.Once确保只执行一次)
- [5.5 sync.Map并发安全Map](#5.5 sync.Map并发安全Map)
- [6. Context上下文底层原理(★★★★)](#6. Context上下文底层原理(★★★★))
-
- [6.1 Context接口](#6.1 Context接口)
- [6.2 Context实现类型](#6.2 Context实现类型)
- [6.3 emptyCtx根Context](#6.3 emptyCtx根Context)
- [6.4 cancelCtx可取消Context](#6.4 cancelCtx可取消Context)
-
- [6.4.1 WithCancel创建可取消Context](#6.4.1 WithCancel创建可取消Context)
- [6.4.2 propagateCancel取消传播](#6.4.2 propagateCancel取消传播)
- [6.4.3 cancel取消实现](#6.4.3 cancel取消实现)
- [6.5 timerCtx超时Context](#6.5 timerCtx超时Context)
-
- [6.5.1 WithDeadline创建超时Context](#6.5.1 WithDeadline创建超时Context)
- [6.5.2 timerCtx的cancel](#6.5.2 timerCtx的cancel)
- [6.6 valueCtx携带值Context](#6.6 valueCtx携带值Context)
- [6.7 Context取消传播链路图](#6.7 Context取消传播链路图)
- [7. Goroutine栈管理(★★★★)](#7. Goroutine栈管理(★★★★))
-
- [7.1 栈初始大小](#7.1 栈初始大小)
- [7.2 栈扩容](#7.2 栈扩容)
- [7.3 栈缩容](#7.3 栈缩容)
- [8. 本地项目源码并发分析](#8. 本地项目源码并发分析)
-
- [8.1 net/http包中的并发使用](#8.1 net/http包中的并发使用)
- [8.2 sync包中的实际应用](#8.2 sync包中的实际应用)
- [8.3 time包中的Timer与Channel](#8.3 time包中的Timer与Channel)
- [9. 面试高频问答(★★★★★)](#9. 面试高频问答(★★★★★))
-
- [9.1 GMP调度器专题](#9.1 GMP调度器专题)
- [9.2 Channel专题](#9.2 Channel专题)
- [9.3 sync包专题](#9.3 sync包专题)
- [9.4 Context专题](#9.4 Context专题)
- [9.5 综合题](#9.5 综合题)
- [10. 踩坑总结与性能优化](#10. 踩坑总结与性能优化)
-
- [10.1 常见踩坑](#10.1 常见踩坑)
- [10.2 性能优化建议](#10.2 性能优化建议)
- 总结
1. 面试考情总览
1.1 高频考点(★★★★★)
| 考点 | 考察概率 | 重点考察内容 |
|---|---|---|
| GMP调度器模型 | 95% | G/M/P三者关系、调度流程、工作窃取、抢占调度 |
| Channel底层实现 | 90% | hchan结构、收发流程、关闭机制、无缓冲/有缓冲区别 |
| sync.Mutex底层 | 88% | 正常/饥饿模式、状态位操作、自旋逻辑 |
| Goroutine栈管理 | 85% | 栈初始大小、扩容/缩容机制、栈拷贝 |
| Context原理 | 82% | 取消传播、父子关系、timerCtx实现 |
1.2 次高频考点(★★★★)
| 考点 | 考察概率 | 重点考察内容 |
|---|---|---|
| sync.RWMutex | 78% | 读写锁饥饿问题、readerCount/writerSem作用 |
| sync.WaitGroup | 75% | state字段位操作、信号量唤醒 |
| sync.Once | 72% | 双重检查、done标志位 |
| sync.Map | 70% | 读写分离、expunged标志、miss计数 |
| Goroutine泄漏 | 80% | 泄漏场景、排查方法、预防措施 |
1.3 进阶考点(★★★)
| 考点 | 考察概率 | 重点考察内容 |
|---|---|---|
| sysmon监控线程 | 65% | 系统监控内容、时间调整、netpoll |
| netpoller模型 | 60% | 非阻塞IO、事件驱动、epoll/kqueue |
| CSP vs 共享内存 | 55% | 设计理念、适用场景 |
| race detector原理 | 50% | happens-before、向量时钟 |
2. 基础理论层
2.1 Goroutine基础
Goroutine是Go语言中的轻量级线程,由Go运行时调度。
go
// 启动一个Goroutine
go func() {
// do something
}()
核心特点:
- 初始栈大小: 2KB (Go 1.4+)
- 栈自动扩容/缩容
- 用户态调度,由Go runtime管理
- 上下文切换代价远小于OS线程
2.2 Channel基础
Channel是Goroutine之间通信的管道,基于CSP模型。
go
// 无缓冲Channel
ch1 := make(chan int)
// 有缓冲Channel
ch2 := make(chan int, 10)
// 单向Channel
var sendCh chan<- int
var recvCh <-chan int
2.3 同步原语基础
sync包提供的核心同步原语:
- Mutex: 互斥锁
- RWMutex: 读写锁
- WaitGroup: 等待一组Goroutine完成
- Once: 确保某函数只执行一次
- Cond: 条件变量
- Map: 并发安全的Map
3. GMP调度器完整底层原理(★★★★★)
3.1 G/M/P核心结构
3.1.1 G结构体(Goroutine)
基于 src/runtime/runtime2.go 源码:
go
type g struct {
// 栈内存
stack stack // 栈范围: [stack.lo, stack.hi)
stackguard0 uintptr // 栈增长/抢占检查点
stackguard1 uintptr // 系统栈检查点
// Goroutine状态管理
_panic *_panic // panic链表
_defer *_defer // defer链表
m *m // 当前绑定的M
sched gobuf // 调度上下文,用于保存/恢复寄存器
atomicstatus atomic.Uint32 // G状态
// Goroutine标识
goid uint64 // Goroutine ID
waitsince int64 // 阻塞开始时间
waitreason waitReason // 阻塞原因
// 调度相关
preempt bool // 抢占标志
preemptStop bool // 抢占后暂停标志
preemptShrink bool // 同步安全点收缩栈标志
// ... 其他字段
}
G状态流转:
#mermaid-svg-rGqXhPodXkd1OpJU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rGqXhPodXkd1OpJU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rGqXhPodXkd1OpJU .error-icon{fill:#552222;}#mermaid-svg-rGqXhPodXkd1OpJU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rGqXhPodXkd1OpJU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rGqXhPodXkd1OpJU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU .marker.cross{stroke:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rGqXhPodXkd1OpJU p{margin:0;}#mermaid-svg-rGqXhPodXkd1OpJU defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-rGqXhPodXkd1OpJU g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-rGqXhPodXkd1OpJU g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-rGqXhPodXkd1OpJU g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-rGqXhPodXkd1OpJU g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-rGqXhPodXkd1OpJU .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-rGqXhPodXkd1OpJU .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-rGqXhPodXkd1OpJU .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-rGqXhPodXkd1OpJU .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rGqXhPodXkd1OpJU .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-rGqXhPodXkd1OpJU .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-rGqXhPodXkd1OpJU .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-rGqXhPodXkd1OpJU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rGqXhPodXkd1OpJU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rGqXhPodXkd1OpJU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rGqXhPodXkd1OpJU .edgeLabel .label text{fill:#333;}#mermaid-svg-rGqXhPodXkd1OpJU .label div .edgeLabel{color:#333;}#mermaid-svg-rGqXhPodXkd1OpJU .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-rGqXhPodXkd1OpJU .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-rGqXhPodXkd1OpJU .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-rGqXhPodXkd1OpJU .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rGqXhPodXkd1OpJU .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rGqXhPodXkd1OpJU #statediagram-barbEnd{fill:#333333;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rGqXhPodXkd1OpJU .cluster-label,#mermaid-svg-rGqXhPodXkd1OpJU .nodeLabel{color:#131300;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-rGqXhPodXkd1OpJU .note-edge{stroke-dasharray:5;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-note text{fill:black;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram-note .nodeLabel{color:black;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagram .edgeLabel{color:red;}#mermaid-svg-rGqXhPodXkd1OpJU #dependencyStart,#mermaid-svg-rGqXhPodXkd1OpJU #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-rGqXhPodXkd1OpJU .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rGqXhPodXkd1OpJU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新建
ready()
execute()
gosched()
entersyscall()
gopark()
exitsyscall()
goready()
goexit()
gfput()
_Gidle
_Grunnable
_Grunning
_Gsyscall
_Gwaiting
_Gdead
G状态常量 (来自 src/runtime/runtime2.go):
| 状态 | 说明 |
|---|---|
| _Gidle | 刚分配,未初始化 |
| _Grunnable | 在运行队列中,等待运行 |
| _Grunning | 正在运行,拥有M和P |
| _Gsyscall | 正在执行系统调用 |
| _Gwaiting | 运行时阻塞,不在运行队列 |
| _Gdead | 未使用,可能在空闲列表中 |
| _Gcopystack | 栈正在被移动 |
| _Gpreempted | 被抢占,暂停执行 |
3.1.2 M结构体(Machine/OS线程)
go
type m struct {
g0 *g // 持有调度栈的Goroutine,用于执行调度代码
morebuf gobuf // morestack的暂存区
curg *g // 当前运行的用户Goroutine
// 调度相关
p puintptr // 当前绑定的P
nextp puintptr // 暂存的P
id int64 // M ID
spinning bool // 是否正在寻找Goroutine(自旋状态)
// 系统相关
procid uint64 // 内核线程ID
threadptr uintptr // OS线程相关数据
// 锁相关
locks int32 // 持有runtime锁的数量
// ... 其他字段
}
g0的特殊作用:
- 每个M都有一个g0
- g0使用固定大小的系统栈(通常8KB)
- 执行调度代码、栈扩容、GC等操作时切换到g0栈
- g0不会被抢占调度
3.1.3 P结构体(Processor)
go
type p struct {
id int32
status uint32 // P状态: _Pidle/_Prunning/_Psyscall/_Pgcstop/_Pdead
link puintptr
schedtick uint32 // 调度器tick,每次调度递增
syscalltick uint32 // 系统调用tick
m muintptr // 当前绑定的M
// 本地运行队列
runqhead uint32
runqtail uint32
runq [256]guintptr // 本地运行队列,最多256个G
runnext guintptr // 下一个运行的G,优先级更高
// 可用G缓存
gFree struct {
gList
n int32
}
// GC相关
gcAssistTime int64
gcMarkWorkerMode gcMarkWorkerMode
gcMarkWorkerStartTime int64
// timer相关
timers timers
// ... 其他字段
}
P状态常量:
- _Pidle: P空闲,在空闲P列表中
- _Prunning: P正在运行,被某个M持有
- _Psyscall: P的G正在执行系统调用
- _Pgcstop: P因GC停止
- _Pdead: P不再使用(GOMAXPROCS减小)
3.2 GMP整体架构
#mermaid-svg-SQ5glz4N91q9QjFC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SQ5glz4N91q9QjFC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SQ5glz4N91q9QjFC .error-icon{fill:#552222;}#mermaid-svg-SQ5glz4N91q9QjFC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SQ5glz4N91q9QjFC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SQ5glz4N91q9QjFC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SQ5glz4N91q9QjFC .marker.cross{stroke:#333333;}#mermaid-svg-SQ5glz4N91q9QjFC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SQ5glz4N91q9QjFC p{margin:0;}#mermaid-svg-SQ5glz4N91q9QjFC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SQ5glz4N91q9QjFC .cluster-label text{fill:#333;}#mermaid-svg-SQ5glz4N91q9QjFC .cluster-label span{color:#333;}#mermaid-svg-SQ5glz4N91q9QjFC .cluster-label span p{background-color:transparent;}#mermaid-svg-SQ5glz4N91q9QjFC .label text,#mermaid-svg-SQ5glz4N91q9QjFC span{fill:#333;color:#333;}#mermaid-svg-SQ5glz4N91q9QjFC .node rect,#mermaid-svg-SQ5glz4N91q9QjFC .node circle,#mermaid-svg-SQ5glz4N91q9QjFC .node ellipse,#mermaid-svg-SQ5glz4N91q9QjFC .node polygon,#mermaid-svg-SQ5glz4N91q9QjFC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SQ5glz4N91q9QjFC .rough-node .label text,#mermaid-svg-SQ5glz4N91q9QjFC .node .label text,#mermaid-svg-SQ5glz4N91q9QjFC .image-shape .label,#mermaid-svg-SQ5glz4N91q9QjFC .icon-shape .label{text-anchor:middle;}#mermaid-svg-SQ5glz4N91q9QjFC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SQ5glz4N91q9QjFC .rough-node .label,#mermaid-svg-SQ5glz4N91q9QjFC .node .label,#mermaid-svg-SQ5glz4N91q9QjFC .image-shape .label,#mermaid-svg-SQ5glz4N91q9QjFC .icon-shape .label{text-align:center;}#mermaid-svg-SQ5glz4N91q9QjFC .node.clickable{cursor:pointer;}#mermaid-svg-SQ5glz4N91q9QjFC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SQ5glz4N91q9QjFC .arrowheadPath{fill:#333333;}#mermaid-svg-SQ5glz4N91q9QjFC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SQ5glz4N91q9QjFC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SQ5glz4N91q9QjFC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SQ5glz4N91q9QjFC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SQ5glz4N91q9QjFC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SQ5glz4N91q9QjFC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SQ5glz4N91q9QjFC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SQ5glz4N91q9QjFC .cluster text{fill:#333;}#mermaid-svg-SQ5glz4N91q9QjFC .cluster span{color:#333;}#mermaid-svg-SQ5glz4N91q9QjFC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SQ5glz4N91q9QjFC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SQ5glz4N91q9QjFC rect.text{fill:none;stroke-width:0;}#mermaid-svg-SQ5glz4N91q9QjFC .icon-shape,#mermaid-svg-SQ5glz4N91q9QjFC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SQ5glz4N91q9QjFC .icon-shape p,#mermaid-svg-SQ5glz4N91q9QjFC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SQ5glz4N91q9QjFC .icon-shape .label rect,#mermaid-svg-SQ5glz4N91q9QjFC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SQ5glz4N91q9QjFC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SQ5glz4N91q9QjFC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SQ5glz4N91q9QjFC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} M1
P2
P1
全局调度
窃取/放入
窃取/放入
持有
调度
绑定
M2
g0
系统调用中
全局运行队列(Global Run Queue)
空闲P列表
本地运行队列
(Local Run Queue)
G1
G2
G3
本地运行队列
G4
G5
g0(调度栈)
用户G运行中
3.3 核心组件对比表
| 组件 | 全称 | 作用 | 数量限制 |
|---|---|---|---|
| G | Goroutine | 用户级轻量级线程 | 几乎无限制(可上百万) |
| M | Machine | 操作系统线程 | 受内核限制(通常几千) |
| P | Processor | 处理器资源,包含运行队列 | GOMAXPROCS(默认CPU核心数) |
3.4 调度循环(schedule)
基于 src/runtime/proc.go 源码:
go
// 调度器主循环: 寻找可运行的Goroutine并执行
func schedule() {
mp := getg().m
top:
pp := mp.p.ptr()
pp.preempt = false
// 寻找可运行的Goroutine
gp, inheritTime, tryWakeP := findRunnable()
// ... 各种状态检查 ...
// 执行Goroutine
execute(gp, inheritTime)
}
3.5 findRunnable寻找可运行G
go
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
mp := getg().m
top:
pp := mp.p.ptr()
// 1. 检查是否需要GC停止
if sched.gcwaiting.Load() {
gcstopm()
goto top
}
// 2. 每61次调度检查全局运行队列(保证公平性)
if pp.schedtick%61 == 0 && !sched.runq.empty() {
lock(&sched.lock)
gp := globrunqget()
unlock(&sched.lock)
if gp != nil {
return gp, false, false
}
}
// 3. 先从本地运行队列获取
if gp, inheritTime := runqget(pp); gp != nil {
return gp, inheritTime, false
}
// 4. 再从全局运行队列获取
if !sched.runq.empty() {
lock(&sched.lock)
gp, q := globrunqgetbatch(int32(len(pp.runq)) / 2)
unlock(&sched.lock)
if gp != nil {
if runqputbatch(pp, &q); !q.empty() {
throw("Couldn't put Gs into empty local runq")
}
return gp, false, false
}
}
// 5. 网络轮询器,检查是否有就绪的网络IO
if netpollinited() && netpollAnyWaiters() && sched.lastpoll.Load() != 0 {
list, delta := netpoll(0) // 非阻塞检查
if !list.empty() {
gp := list.pop()
injectglist(&list)
return gp, false, false
}
}
// 6. 工作窃取: 从其他P的本地队列窃取
if mp.spinning || 2*sched.nmspinning.Load() < gomaxprocs-sched.npidle.Load() {
if !mp.spinning {
mp.becomeSpinning()
}
gp, inheritTime, tnow, w, newWork := stealWork(now)
if gp != nil {
return gp, inheritTime, false
}
// ...
}
// 7. 找不到G,停止当前M
stopm()
goto top
}
3.6 工作窃取(Work Stealing)机制
窃取逻辑 (来自 stealWork 函数):
- 如果当前P的本地队列为空,尝试从其他P窃取
- 随机选择一个P,尝试窃取其本地队列的一半Goroutine
- 如果窃取失败,检查其他P的timer
- 如果找到可运行的timer,运行它
go
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
pp := getg().m.p.ptr()
for i := 0; i < 4; i++ { // 尝试4次
for enum := stealOrder.start(cheaprand()); !enum.done(); enum.next() {
p2 := allp[enum.position()]
if pp == p2 {
continue
}
// 最后一次尝试时,窃取timer
if i == 3 && timerpMask.read(enum.position()) {
// ... 检查timer ...
}
// 尝试从p2窃取Goroutine
if gp := runqsteal(pp, p2, i == 3); gp != nil {
return gp, false, now, pollUntil, false
}
}
}
return nil, false, now, pollUntil, false
}
窃取策略:
- 从目标P的本地队列窃取一半(向上取整)Goroutine
- 窃取的G放入当前P的本地队列
- 只返回一个G立即执行,其余放在本地队列
3.7 Goroutine创建流程
go
// 创建新Goroutine的入口函数
func newproc(siz int32, fn *funcval) {
// 获取调用者的Goroutine
gp := getg()
// 创建新的Goroutine对象
newg := gfget(gp.m.p.ptr())
if newg == nil {
newg = malg(_StackMin) // 分配2KB初始栈
casgstatus(newg, _Gidle, _Gdead)
allgadd(newg)
}
// 初始化新G的栈和调度上下文
// ...
// 将新G放入当前P的runnext或本地队列
runqput(gp.m.p.ptr(), newg, true)
// 如果有空闲P,尝试唤醒一个M
if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 {
wakep()
}
}
3.8 抢占式调度
3.8.1 异步抢占
Go 1.14+ 实现了基于信号的异步抢占:
操作系统 M线程 用户Goroutine sysmon线程 操作系统 M线程 用户Goroutine sysmon线程 #mermaid-svg-kEhdXVzwamIn7BtQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kEhdXVzwamIn7BtQ .error-icon{fill:#552222;}#mermaid-svg-kEhdXVzwamIn7BtQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kEhdXVzwamIn7BtQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kEhdXVzwamIn7BtQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kEhdXVzwamIn7BtQ .marker.cross{stroke:#333333;}#mermaid-svg-kEhdXVzwamIn7BtQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kEhdXVzwamIn7BtQ p{margin:0;}#mermaid-svg-kEhdXVzwamIn7BtQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kEhdXVzwamIn7BtQ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kEhdXVzwamIn7BtQ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kEhdXVzwamIn7BtQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kEhdXVzwamIn7BtQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kEhdXVzwamIn7BtQ .sequenceNumber{fill:white;}#mermaid-svg-kEhdXVzwamIn7BtQ #sequencenumber{fill:#333;}#mermaid-svg-kEhdXVzwamIn7BtQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kEhdXVzwamIn7BtQ .messageText{fill:#333;stroke:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kEhdXVzwamIn7BtQ .labelText,#mermaid-svg-kEhdXVzwamIn7BtQ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .loopText,#mermaid-svg-kEhdXVzwamIn7BtQ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kEhdXVzwamIn7BtQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kEhdXVzwamIn7BtQ .noteText,#mermaid-svg-kEhdXVzwamIn7BtQ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kEhdXVzwamIn7BtQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kEhdXVzwamIn7BtQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kEhdXVzwamIn7BtQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kEhdXVzwamIn7BtQ .actorPopupMenu{position:absolute;}#mermaid-svg-kEhdXVzwamIn7BtQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kEhdXVzwamIn7BtQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kEhdXVzwamIn7BtQ .actor-man circle,#mermaid-svg-kEhdXVzwamIn7BtQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kEhdXVzwamIn7BtQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt G运行超过10ms 每10ms检查Goroutine运行时间 发送SIGURG信号 信号传递给M 触发asyncPreempt 保存上下文 进入_Gpreempted状态 调用gopreempt_m 将G放回队列 schedule()调度新G
sysmon监控线程 (src/runtime/proc.go):
sysmon是一个独立的M,不绑定P,循环执行以下任务:
- 检查长时间运行的Goroutine(>10ms),触发抢占
- 检查在系统调用中阻塞超过20us的P,将P偷取给其他M
- 检查死锁
- 强制GC(如果需要)
3.8.2 同步安全点抢占
在函数调用序言中检查栈空间时也可以触发抢占:
go
// 栈增长检查
func morestack() {
// ...
if gp.stackguard0 == stackPreempt {
// 触发抢占
asyncPreempt()
}
// ...
}
3.9 系统调用时的P处理
#mermaid-svg-WY8r3NpnD9OUDD0v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WY8r3NpnD9OUDD0v .error-icon{fill:#552222;}#mermaid-svg-WY8r3NpnD9OUDD0v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WY8r3NpnD9OUDD0v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WY8r3NpnD9OUDD0v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WY8r3NpnD9OUDD0v .marker.cross{stroke:#333333;}#mermaid-svg-WY8r3NpnD9OUDD0v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WY8r3NpnD9OUDD0v p{margin:0;}#mermaid-svg-WY8r3NpnD9OUDD0v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster-label text{fill:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster-label span{color:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster-label span p{background-color:transparent;}#mermaid-svg-WY8r3NpnD9OUDD0v .label text,#mermaid-svg-WY8r3NpnD9OUDD0v span{fill:#333;color:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v .node rect,#mermaid-svg-WY8r3NpnD9OUDD0v .node circle,#mermaid-svg-WY8r3NpnD9OUDD0v .node ellipse,#mermaid-svg-WY8r3NpnD9OUDD0v .node polygon,#mermaid-svg-WY8r3NpnD9OUDD0v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WY8r3NpnD9OUDD0v .rough-node .label text,#mermaid-svg-WY8r3NpnD9OUDD0v .node .label text,#mermaid-svg-WY8r3NpnD9OUDD0v .image-shape .label,#mermaid-svg-WY8r3NpnD9OUDD0v .icon-shape .label{text-anchor:middle;}#mermaid-svg-WY8r3NpnD9OUDD0v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WY8r3NpnD9OUDD0v .rough-node .label,#mermaid-svg-WY8r3NpnD9OUDD0v .node .label,#mermaid-svg-WY8r3NpnD9OUDD0v .image-shape .label,#mermaid-svg-WY8r3NpnD9OUDD0v .icon-shape .label{text-align:center;}#mermaid-svg-WY8r3NpnD9OUDD0v .node.clickable{cursor:pointer;}#mermaid-svg-WY8r3NpnD9OUDD0v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WY8r3NpnD9OUDD0v .arrowheadPath{fill:#333333;}#mermaid-svg-WY8r3NpnD9OUDD0v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WY8r3NpnD9OUDD0v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WY8r3NpnD9OUDD0v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WY8r3NpnD9OUDD0v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WY8r3NpnD9OUDD0v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WY8r3NpnD9OUDD0v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster text{fill:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v .cluster span{color:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WY8r3NpnD9OUDD0v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WY8r3NpnD9OUDD0v rect.text{fill:none;stroke-width:0;}#mermaid-svg-WY8r3NpnD9OUDD0v .icon-shape,#mermaid-svg-WY8r3NpnD9OUDD0v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WY8r3NpnD9OUDD0v .icon-shape p,#mermaid-svg-WY8r3NpnD9OUDD0v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WY8r3NpnD9OUDD0v .icon-shape .label rect,#mermaid-svg-WY8r3NpnD9OUDD0v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WY8r3NpnD9OUDD0v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WY8r3NpnD9OUDD0v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WY8r3NpnD9OUDD0v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} entersyscall
syscall > 20us
syscall < 20us
成功
失败
G执行系统调用
G进入_Gsyscall状态
P与M解绑,P变为_Psyscall
sysmon监控线程检查
P被拿走,变为_Pidle
其他M获取P继续执行其他G
M从系统调用返回
尝试获取原来的P
继续执行原来的G
从空闲列表获取P
源码流程:
go
// 进入系统调用
func entersyscall() {
// ...
casgstatus(gp, _Grunning, _Gsyscall)
pp := mp.p.ptr()
pp.m = 0
mp.p.set(nil)
pp.status = _Psyscall
// ...
}
// 退出系统调用
func exitsyscall() {
// ...
// 首先尝试获取原来的P
if atomic.Cas(&pp.status, _Psyscall, _Prunning) {
pp.m.set(mp)
mp.p.set(pp)
casgstatus(gp, _Gsyscall, _Grunning)
return
}
// 如果失败,获取其他空闲P
if pidleget(0) != nil {
// ...
}
// 如果没有P,将G放入全局队列
globrunqput(gp)
// ...
}
3.10 GOMAXPROCS的作用
GOMAXPROCS决定了P的数量,即同时可以执行用户Goroutine的最大M数量:
go
func GOMAXPROCS(n int) int {
// ...
lock(&sched.lock)
ret := gomaxprocs
if n <= 0 || n == ret {
unlock(&sched.lock)
return ret
}
// 调整P的数量
if n > ret {
// 增加P
for i := ret; i < n; i++ {
// ... 创建新P
}
} else {
// 减少P
for i := n; i < ret; i++ {
// ... 释放多余的P
}
}
// ...
}
最佳实践:
- GOMAXPROCS 默认等于 CPU 核心数
- 通常不需要修改
- CPU 密集型: 保持默认或稍小
- IO 密集型: 可以适当增大,但不会太大(因为P在IO时会被释放)
4. Channel底层原理(★★★★★)
4.1 hchan结构体
基于 src/runtime/chan.go 源码:
go
type hchan struct {
qcount uint // 队列中的元素个数
dataqsiz uint // 环形队列的大小
buf unsafe.Pointer // 指向大小为dataqsiz的数组
elemsize uint16 // 元素大小
closed uint32 // 是否已关闭
elemtype *_type // 元素类型信息
sendx uint // 发送索引(下一个发送的位置)
recvx uint // 接收索引(下一个接收的位置)
recvq waitq // 接收等待队列
sendq waitq // 发送等待队列
lock mutex // 互斥锁
}
环形队列结构:
#mermaid-svg-yxplfcExBEDcqd8T{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yxplfcExBEDcqd8T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yxplfcExBEDcqd8T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yxplfcExBEDcqd8T .error-icon{fill:#552222;}#mermaid-svg-yxplfcExBEDcqd8T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yxplfcExBEDcqd8T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yxplfcExBEDcqd8T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yxplfcExBEDcqd8T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yxplfcExBEDcqd8T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yxplfcExBEDcqd8T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yxplfcExBEDcqd8T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yxplfcExBEDcqd8T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yxplfcExBEDcqd8T .marker.cross{stroke:#333333;}#mermaid-svg-yxplfcExBEDcqd8T svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yxplfcExBEDcqd8T p{margin:0;}#mermaid-svg-yxplfcExBEDcqd8T .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yxplfcExBEDcqd8T .cluster-label text{fill:#333;}#mermaid-svg-yxplfcExBEDcqd8T .cluster-label span{color:#333;}#mermaid-svg-yxplfcExBEDcqd8T .cluster-label span p{background-color:transparent;}#mermaid-svg-yxplfcExBEDcqd8T .label text,#mermaid-svg-yxplfcExBEDcqd8T span{fill:#333;color:#333;}#mermaid-svg-yxplfcExBEDcqd8T .node rect,#mermaid-svg-yxplfcExBEDcqd8T .node circle,#mermaid-svg-yxplfcExBEDcqd8T .node ellipse,#mermaid-svg-yxplfcExBEDcqd8T .node polygon,#mermaid-svg-yxplfcExBEDcqd8T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yxplfcExBEDcqd8T .rough-node .label text,#mermaid-svg-yxplfcExBEDcqd8T .node .label text,#mermaid-svg-yxplfcExBEDcqd8T .image-shape .label,#mermaid-svg-yxplfcExBEDcqd8T .icon-shape .label{text-anchor:middle;}#mermaid-svg-yxplfcExBEDcqd8T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yxplfcExBEDcqd8T .rough-node .label,#mermaid-svg-yxplfcExBEDcqd8T .node .label,#mermaid-svg-yxplfcExBEDcqd8T .image-shape .label,#mermaid-svg-yxplfcExBEDcqd8T .icon-shape .label{text-align:center;}#mermaid-svg-yxplfcExBEDcqd8T .node.clickable{cursor:pointer;}#mermaid-svg-yxplfcExBEDcqd8T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yxplfcExBEDcqd8T .arrowheadPath{fill:#333333;}#mermaid-svg-yxplfcExBEDcqd8T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yxplfcExBEDcqd8T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yxplfcExBEDcqd8T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yxplfcExBEDcqd8T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yxplfcExBEDcqd8T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yxplfcExBEDcqd8T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yxplfcExBEDcqd8T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yxplfcExBEDcqd8T .cluster text{fill:#333;}#mermaid-svg-yxplfcExBEDcqd8T .cluster span{color:#333;}#mermaid-svg-yxplfcExBEDcqd8T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yxplfcExBEDcqd8T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yxplfcExBEDcqd8T rect.text{fill:none;stroke-width:0;}#mermaid-svg-yxplfcExBEDcqd8T .icon-shape,#mermaid-svg-yxplfcExBEDcqd8T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yxplfcExBEDcqd8T .icon-shape p,#mermaid-svg-yxplfcExBEDcqd8T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yxplfcExBEDcqd8T .icon-shape .label rect,#mermaid-svg-yxplfcExBEDcqd8T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yxplfcExBEDcqd8T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yxplfcExBEDcqd8T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yxplfcExBEDcqd8T :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} hchan.buf
索引0
索引1
索引2
索引3
索引4
sendx
recvx
4.2 无缓冲与有缓冲Channel对比
| 特性 | 无缓冲Channel | 有缓冲Channel |
|---|---|---|
| make语法 | make(chan T) | make(chan T, n) |
| 同步/异步 | 同步,需双方就绪 | 异步,缓冲区未满不阻塞 |
| buf大小 | 0 | n |
| qcount范围 | 0或1 | 0~n |
| 阻塞场景 | 无接收者时发送 无发送者时接收 | 缓冲区满时发送 缓冲区空时接收 |
4.3 Channel发送流程
基于 chansend 函数源码:
#mermaid-svg-eHGbrWYQHPA3BBOv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eHGbrWYQHPA3BBOv .error-icon{fill:#552222;}#mermaid-svg-eHGbrWYQHPA3BBOv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eHGbrWYQHPA3BBOv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eHGbrWYQHPA3BBOv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eHGbrWYQHPA3BBOv .marker.cross{stroke:#333333;}#mermaid-svg-eHGbrWYQHPA3BBOv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eHGbrWYQHPA3BBOv p{margin:0;}#mermaid-svg-eHGbrWYQHPA3BBOv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster-label text{fill:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster-label span{color:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster-label span p{background-color:transparent;}#mermaid-svg-eHGbrWYQHPA3BBOv .label text,#mermaid-svg-eHGbrWYQHPA3BBOv span{fill:#333;color:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv .node rect,#mermaid-svg-eHGbrWYQHPA3BBOv .node circle,#mermaid-svg-eHGbrWYQHPA3BBOv .node ellipse,#mermaid-svg-eHGbrWYQHPA3BBOv .node polygon,#mermaid-svg-eHGbrWYQHPA3BBOv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eHGbrWYQHPA3BBOv .rough-node .label text,#mermaid-svg-eHGbrWYQHPA3BBOv .node .label text,#mermaid-svg-eHGbrWYQHPA3BBOv .image-shape .label,#mermaid-svg-eHGbrWYQHPA3BBOv .icon-shape .label{text-anchor:middle;}#mermaid-svg-eHGbrWYQHPA3BBOv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eHGbrWYQHPA3BBOv .rough-node .label,#mermaid-svg-eHGbrWYQHPA3BBOv .node .label,#mermaid-svg-eHGbrWYQHPA3BBOv .image-shape .label,#mermaid-svg-eHGbrWYQHPA3BBOv .icon-shape .label{text-align:center;}#mermaid-svg-eHGbrWYQHPA3BBOv .node.clickable{cursor:pointer;}#mermaid-svg-eHGbrWYQHPA3BBOv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eHGbrWYQHPA3BBOv .arrowheadPath{fill:#333333;}#mermaid-svg-eHGbrWYQHPA3BBOv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eHGbrWYQHPA3BBOv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eHGbrWYQHPA3BBOv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eHGbrWYQHPA3BBOv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eHGbrWYQHPA3BBOv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eHGbrWYQHPA3BBOv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster text{fill:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv .cluster span{color:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eHGbrWYQHPA3BBOv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eHGbrWYQHPA3BBOv rect.text{fill:none;stroke-width:0;}#mermaid-svg-eHGbrWYQHPA3BBOv .icon-shape,#mermaid-svg-eHGbrWYQHPA3BBOv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eHGbrWYQHPA3BBOv .icon-shape p,#mermaid-svg-eHGbrWYQHPA3BBOv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eHGbrWYQHPA3BBOv .icon-shape .label rect,#mermaid-svg-eHGbrWYQHPA3BBOv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eHGbrWYQHPA3BBOv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eHGbrWYQHPA3BBOv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eHGbrWYQHPA3BBOv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
有
无
是
否
c已关闭
成功
ch = c <- x
c == nil?
gopark阻塞(永远)
加锁 c.lock
c已关闭?
解锁, panic
接收队列有等待者?
直接发送给等待者
sendDirect 复制值
goready 唤醒接收G
解锁,返回
缓冲区未满?
将值写入缓冲区 c.sendx位置
c.sendx++
c.qcount++
将当前G加入sendq
gopark阻塞等待
被唤醒后检查
panic
返回
源码详解:
go
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
// 1. 检查nil channel
if c == nil {
if !block {
return false
}
gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)
throw("unreachable")
}
// 2. 非阻塞模式的快速路径检查
if !block && c.closed == 0 && full(c) {
return false
}
// 3. 加锁
lock(&c.lock)
// 4. 检查是否已关闭
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
// 5. 直接发送: 如果有等待的接收者
if sg := c.recvq.dequeue(); sg != nil {
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
// 6. 写入缓冲区: 如果缓冲区有空间
if c.qcount < c.dataqsiz {
qp := chanbuf(c, c.sendx)
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
// 7. 非阻塞,直接返回
if !block {
unlock(&c.lock)
return false
}
// 8. 阻塞等待: 构造sudog,加入发送队列
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
mysg.elem.set(ep)
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c.set(c)
gp.waiting = mysg
gp.param = nil
c.sendq.enqueue(mysg)
// 9. park当前Goroutine
gp.parkingOnChan.Store(true)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceBlockChanSend, 2)
// 10. 被唤醒后的检查
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
closed := !mysg.success
gp.param = nil
mysg.c.set(nil)
releaseSudog(mysg)
if closed {
if c.closed == 0 {
throw("chansend: spurious wakeup")
}
panic(plainError("send on closed channel"))
}
return true
}
4.4 Channel接收流程
go
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// 1. nil channel处理
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceBlockForever, 2)
throw("unreachable")
}
// 2. 非阻塞模式的快速路径检查
if !block && empty(c) {
if atomic.Load(&c.closed) == 0 {
return
}
if empty(c) {
if raceenabled {
raceacquire(c.raceaddr())
}
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
}
// 3. 加锁
lock(&c.lock)
// 4. 检查是否已关闭且无数据
if c.closed != 0 && c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
// 5. 直接接收: 有等待的发送者
if sg := c.sendq.dequeue(); sg != nil {
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
// 6. 从缓冲区读取数据
if c.qcount > 0 {
qp := chanbuf(c, c.recvx)
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
typedmemclr(c.elemtype, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.qcount--
unlock(&c.lock)
return true, true
}
// 7. 非阻塞模式返回
if !block {
unlock(&c.lock)
return false, false
}
// 8. 阻塞等待
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
mysg.elem.set(ep)
mysg.g = gp
mysg.isSelect = false
mysg.c.set(c)
gp.waiting = mysg
gp.param = nil
c.recvq.enqueue(mysg)
// 9. park
gp.parkingOnChan.Store(true)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceBlockChanRecv, 2)
// 10. 被唤醒
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
success := mysg.success
gp.param = nil
mysg.c.set(nil)
releaseSudog(mysg)
return true, success
}
4.5 关闭Channel
go
func closechan(c *hchan) {
// 1. 检查nil
if c == nil {
panic(plainError("close of nil channel"))
}
// 2. 加锁
lock(&c.lock)
// 3. 检查重复关闭
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
// 4. 标记关闭
c.closed = 1
var glist gList
// 5. 释放所有接收者
for {
sg := c.recvq.dequeue()
if sg == nil {
break
}
if sg.elem.get() != nil {
typedmemclr(c.elemtype, sg.elem.get())
sg.elem.set(nil)
}
gp := sg.g
gp.param = unsafe.Pointer(sg)
sg.success = false
glist.push(gp)
}
// 6. 释放所有发送者(会panic)
for {
sg := c.sendq.dequeue()
if sg == nil {
break
}
sg.elem.set(nil)
gp := sg.g
gp.param = unsafe.Pointer(sg)
sg.success = false
glist.push(gp)
}
// 7. 解锁
unlock(&c.lock)
// 8. 唤醒所有等待的Goroutine
for !glist.empty() {
gp := glist.pop()
gp.schedlink = 0
goready(gp, 3)
}
}
4.6 nil/closed channel操作对照表
| 操作 | nil channel | closed channel | 正常channel |
|---|---|---|---|
| ch <- v | 永久阻塞 | panic | 阻塞或成功 |
| v := <-ch | 永久阻塞 | 零值,ok=false | 阻塞或成功 |
| close(ch) | panic | panic | 成功关闭 |
| len(ch) | 0 | 剩余元素数 | 队列元素数 |
| cap(ch) | 0 | 缓冲区大小 | 缓冲区大小 |
4.7 Channel内存泄漏场景
常见泄漏场景:
go
// 场景1: Goroutine等待永远不会到来的消息
func leak1() {
ch := make(chan int)
go func() {
<-ch // 永远阻塞,如果没有发送者
}()
}
// 场景2: select中有一个nil channel的case
func leak2() {
var ch chan int
go func() {
select {
case <-ch: // nil channel,永远阻塞
}
}()
}
// 场景3: 没有接收者,发送者阻塞
func leak3() {
ch := make(chan int)
go func() {
ch <- 1 // 永远阻塞
}()
}
5. sync包同步原语底层原理(★★★★★)
5.1 sync.Mutex互斥锁
基于 src/internal/sync/mutex.go 源码:
go
type Mutex struct {
state int32 // 状态位
sema uint32 // 信号量
}
const (
mutexLocked = 1 << iota // 锁持有标志
mutexWoken // 唤醒标志
mutexStarving // 饥饿模式标志
mutexWaiterShift = iota // 等待者数量移位
)
state字段位布局:
31..3 2 1 0
+-------+----------------+-----------+----------+
| waiters | mutexStarving | mutexWoken| locked |
+-------+----------------+-----------+----------+
5.1.1 Mutex两种模式对比
| 特性 | 正常模式 | 饥饿模式 |
|---|---|---|
| 调度方式 | 新G可抢锁 | 直接交给队首等待者 |
| 性能 | 高,可连续获取 | 低,防止尾延迟 |
| 切换条件 | 等待者等待>1ms | 等待者等待<1ms 或 是最后一个等待者 |
| 自旋 | 支持 | 不支持 |
#mermaid-svg-4TMX6sZmCrXyYF9L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4TMX6sZmCrXyYF9L .error-icon{fill:#552222;}#mermaid-svg-4TMX6sZmCrXyYF9L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4TMX6sZmCrXyYF9L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4TMX6sZmCrXyYF9L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L .marker.cross{stroke:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4TMX6sZmCrXyYF9L p{margin:0;}#mermaid-svg-4TMX6sZmCrXyYF9L defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-4TMX6sZmCrXyYF9L g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-4TMX6sZmCrXyYF9L g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-4TMX6sZmCrXyYF9L g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-4TMX6sZmCrXyYF9L g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-4TMX6sZmCrXyYF9L .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-4TMX6sZmCrXyYF9L .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4TMX6sZmCrXyYF9L .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-4TMX6sZmCrXyYF9L .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-4TMX6sZmCrXyYF9L .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-4TMX6sZmCrXyYF9L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4TMX6sZmCrXyYF9L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4TMX6sZmCrXyYF9L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4TMX6sZmCrXyYF9L .edgeLabel .label text{fill:#333;}#mermaid-svg-4TMX6sZmCrXyYF9L .label div .edgeLabel{color:#333;}#mermaid-svg-4TMX6sZmCrXyYF9L .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-4TMX6sZmCrXyYF9L .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-4TMX6sZmCrXyYF9L .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-4TMX6sZmCrXyYF9L .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L #statediagram-barbEnd{fill:#333333;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4TMX6sZmCrXyYF9L .cluster-label,#mermaid-svg-4TMX6sZmCrXyYF9L .nodeLabel{color:#131300;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-4TMX6sZmCrXyYF9L .note-edge{stroke-dasharray:5;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-note text{fill:black;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram-note .nodeLabel{color:black;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagram .edgeLabel{color:red;}#mermaid-svg-4TMX6sZmCrXyYF9L #dependencyStart,#mermaid-svg-4TMX6sZmCrXyYF9L #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-4TMX6sZmCrXyYF9L .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4TMX6sZmCrXyYF9L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始状态
等待者等待>1ms
等待者等待<1ms 或 最后一个等待者
正常模式
饥饿模式
5.1.2 Lock加锁流程
go
func (m *Mutex) Lock() {
// 1. 快速路径: 尝试直接CAS获取锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// 2. 慢速路径
m.lockSlow()
}
func (m *Mutex) lockSlow() {
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state
for {
// 3. 正常模式且锁已被持有: 尝试自旋
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// 设置woken标志,告诉Unlock不要唤醒其他等待者
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
// 4. 非饥饿模式: 尝试获取锁
if old&mutexStarving == 0 {
new |= mutexLocked
}
// 5. 锁已持有或饥饿模式: 增加等待者计数
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift
}
// 6. 当前G饥饿且锁已持有: 切换到饥饿模式
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
// 7. 清除woken标志
if awoke {
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
new &^= mutexWoken
}
// 8. CAS更新状态
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 9. 成功获取锁(之前未锁定且非饥饿模式)
if old&(mutexLocked|mutexStarving) == 0 {
break
}
// 10. 之前等待过: 插入队列头部
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
// 11. 等待信号量
runtime_SemacquireMutex(&m.sema, queueLifo, 2)
// 12. 被唤醒后,检查是否进入饥饿模式
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
// 13. 如果处于饥饿模式,现在该我们持有锁了
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))
}
}
5.1.3 Unlock解锁流程
go
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// 1. 快速路径: 清除locked位
new := atomic.AddInt32(&m.state, -mutexLocked)
if new != 0 {
// 2. 慢速路径
m.unlockSlow(new)
}
}
func (m *Mutex) unlockSlow(new int32) {
// 检查重复unlock
if (new+mutexLocked)&mutexLocked == 0 {
fatal("sync: unlock of unlocked mutex")
}
// 3. 正常模式
if new&mutexStarving == 0 {
old := new
for {
// 没有等待者,或已经有人持有锁/唤醒了/饥饿模式,直接返回
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// 减少等待者,设置woken标志
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 唤醒一个等待者
runtime_Semrelease(&m.sema, false, 2)
return
}
old = m.state
}
} else {
// 4. 饥饿模式: 直接唤醒队首等待者
runtime_Semrelease(&m.sema, true, 2)
}
}
5.1.4 Mutex加锁状态流转图
#mermaid-svg-LDxqDUArbNaX2SoE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LDxqDUArbNaX2SoE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LDxqDUArbNaX2SoE .error-icon{fill:#552222;}#mermaid-svg-LDxqDUArbNaX2SoE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LDxqDUArbNaX2SoE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LDxqDUArbNaX2SoE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LDxqDUArbNaX2SoE .marker.cross{stroke:#333333;}#mermaid-svg-LDxqDUArbNaX2SoE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LDxqDUArbNaX2SoE p{margin:0;}#mermaid-svg-LDxqDUArbNaX2SoE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LDxqDUArbNaX2SoE .cluster-label text{fill:#333;}#mermaid-svg-LDxqDUArbNaX2SoE .cluster-label span{color:#333;}#mermaid-svg-LDxqDUArbNaX2SoE .cluster-label span p{background-color:transparent;}#mermaid-svg-LDxqDUArbNaX2SoE .label text,#mermaid-svg-LDxqDUArbNaX2SoE span{fill:#333;color:#333;}#mermaid-svg-LDxqDUArbNaX2SoE .node rect,#mermaid-svg-LDxqDUArbNaX2SoE .node circle,#mermaid-svg-LDxqDUArbNaX2SoE .node ellipse,#mermaid-svg-LDxqDUArbNaX2SoE .node polygon,#mermaid-svg-LDxqDUArbNaX2SoE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LDxqDUArbNaX2SoE .rough-node .label text,#mermaid-svg-LDxqDUArbNaX2SoE .node .label text,#mermaid-svg-LDxqDUArbNaX2SoE .image-shape .label,#mermaid-svg-LDxqDUArbNaX2SoE .icon-shape .label{text-anchor:middle;}#mermaid-svg-LDxqDUArbNaX2SoE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LDxqDUArbNaX2SoE .rough-node .label,#mermaid-svg-LDxqDUArbNaX2SoE .node .label,#mermaid-svg-LDxqDUArbNaX2SoE .image-shape .label,#mermaid-svg-LDxqDUArbNaX2SoE .icon-shape .label{text-align:center;}#mermaid-svg-LDxqDUArbNaX2SoE .node.clickable{cursor:pointer;}#mermaid-svg-LDxqDUArbNaX2SoE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LDxqDUArbNaX2SoE .arrowheadPath{fill:#333333;}#mermaid-svg-LDxqDUArbNaX2SoE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LDxqDUArbNaX2SoE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LDxqDUArbNaX2SoE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LDxqDUArbNaX2SoE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LDxqDUArbNaX2SoE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LDxqDUArbNaX2SoE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LDxqDUArbNaX2SoE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LDxqDUArbNaX2SoE .cluster text{fill:#333;}#mermaid-svg-LDxqDUArbNaX2SoE .cluster span{color:#333;}#mermaid-svg-LDxqDUArbNaX2SoE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LDxqDUArbNaX2SoE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LDxqDUArbNaX2SoE rect.text{fill:none;stroke-width:0;}#mermaid-svg-LDxqDUArbNaX2SoE .icon-shape,#mermaid-svg-LDxqDUArbNaX2SoE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LDxqDUArbNaX2SoE .icon-shape p,#mermaid-svg-LDxqDUArbNaX2SoE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LDxqDUArbNaX2SoE .icon-shape .label rect,#mermaid-svg-LDxqDUArbNaX2SoE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LDxqDUArbNaX2SoE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LDxqDUArbNaX2SoE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LDxqDUArbNaX2SoE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
是
是
否
否
否
是
饥饿
正常
Unlock
(释放锁)
饥饿模式?
唤醒一个等待者
(如果有)
直接唤醒队首等待者
Lock
CAS获取?
成功获取
锁被持有?
能自旋?
自旋
抢到锁?
加入等待队列
饥饿模式?
尝试获取锁
加入等待队列
等待被唤醒
被唤醒后
直接持有锁
重新竞争
5.2 sync.RWMutex读写锁
基于 src/sync/rwmutex.go 源码:
go
type RWMutex struct {
w Mutex // 用于互斥写操作
writerSem uint32 // 写等待信号量
readerSem uint32 // 读等待信号量
readerCount atomic.Int32 // 读计数: 正数表示读者数量,负数表示有写者等待
readerWait atomic.Int32 // 写者等待的读者数量
}
const rwmutexMaxReaders = 1 << 30 // 最大读者数
readerCount的双重含义:
readerCount > 0: 当前读者数量readerCount < 0: 有写者在等待,值为-(剩余读者数 + rwmutexMaxReaders)
5.2.1 RLock读加锁
go
func (rw *RWMutex) RLock() {
if race.Enabled {
race.Read(unsafe.Pointer(&rw.w))
race.Disable()
}
// 增加读计数
if rw.readerCount.Add(1) < 0 {
// 有写者在等待,阻塞等待
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
5.2.2 RUnlock读解锁
go
func (rw *RWMutex) RUnlock() {
if race.Enabled {
race.Read(unsafe.Pointer(&rw.w))
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// 减少读计数
if r := rw.readerCount.Add(-1); r < 0 {
// 有写者在等待,检查是否是最后一个读者
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// 检查无效操作
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
fatal("sync: RUnlock of unlocked RWMutex")
}
// 最后一个读者,唤醒写者
if rw.readerWait.Add(-1) == 0 {
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
5.2.3 Lock写加锁
go
func (rw *RWMutex) Lock() {
if race.Enabled {
race.Read(unsafe.Pointer(&rw.w))
race.Disable()
}
// 1. 先获取互斥锁,与其他写者互斥
rw.w.Lock()
// 2. 标记有写者等待: readerCount -= rwmutexMaxReaders
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
// 3. 如果有读者,等待它们完成
if r != 0 && rw.readerWait.Add(r) != 0 {
runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
5.2.4 Unlock写解锁
go
func (rw *RWMutex) Unlock() {
if race.Enabled {
race.Read(unsafe.Pointer(&rw.w))
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// 1. 标记写完成: readerCount += rwmutexMaxReaders
r := rw.readerCount.Add(rwmutexMaxReaders)
// 检查无效操作
if r >= rwmutexMaxReaders {
race.Enable()
fatal("sync: Unlock of unlocked RWMutex")
}
// 2. 唤醒所有等待的读者
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 3. 释放互斥锁
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}
5.2.5 RWMutex写饥饿问题
问题: 持续有读者来,写者永远无法获取锁
解决:
- 当有写者等待时,新读者会被阻塞(通过readerCount负值)
- 写者等待所有已存在的读者完成
- 新读者看到readerCount<0会阻塞在readerSem上
读者2 写者 RWMutex 读者1 读者2 写者 RWMutex 读者1 #mermaid-svg-8anxmD1jdzgZ1Rva{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8anxmD1jdzgZ1Rva .error-icon{fill:#552222;}#mermaid-svg-8anxmD1jdzgZ1Rva .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8anxmD1jdzgZ1Rva .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8anxmD1jdzgZ1Rva .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8anxmD1jdzgZ1Rva .marker.cross{stroke:#333333;}#mermaid-svg-8anxmD1jdzgZ1Rva svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8anxmD1jdzgZ1Rva p{margin:0;}#mermaid-svg-8anxmD1jdzgZ1Rva .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8anxmD1jdzgZ1Rva text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8anxmD1jdzgZ1Rva .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8anxmD1jdzgZ1Rva .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8anxmD1jdzgZ1Rva #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8anxmD1jdzgZ1Rva .sequenceNumber{fill:white;}#mermaid-svg-8anxmD1jdzgZ1Rva #sequencenumber{fill:#333;}#mermaid-svg-8anxmD1jdzgZ1Rva #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8anxmD1jdzgZ1Rva .messageText{fill:#333;stroke:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8anxmD1jdzgZ1Rva .labelText,#mermaid-svg-8anxmD1jdzgZ1Rva .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .loopText,#mermaid-svg-8anxmD1jdzgZ1Rva .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8anxmD1jdzgZ1Rva .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8anxmD1jdzgZ1Rva .noteText,#mermaid-svg-8anxmD1jdzgZ1Rva .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8anxmD1jdzgZ1Rva .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8anxmD1jdzgZ1Rva .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8anxmD1jdzgZ1Rva .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8anxmD1jdzgZ1Rva .actorPopupMenu{position:absolute;}#mermaid-svg-8anxmD1jdzgZ1Rva .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8anxmD1jdzgZ1Rva .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8anxmD1jdzgZ1Rva .actor-man circle,#mermaid-svg-8anxmD1jdzgZ1Rva line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8anxmD1jdzgZ1Rva :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RLock() readerCount = 1 Lock() 先获取w.Mutex readerCount -= 1<<30 readerCount = 1 - 1<<30 (负数) readerWait = 1 等待信号量(阻塞) RLock() readerCount += 1 → 仍然是负数 阻塞在readerSem RUnlock() readerCount -= 1 readerWait -= 1 → 0 唤醒 获取锁成功
5.3 sync.WaitGroup
基于 src/sync/waitgroup.go 源码:
go
type WaitGroup struct {
noCopy noCopy
// state字段分为两部分:
// 高32位: 计数器
// 低32位: 等待者数量
state atomic.Uint64
sema uint32
}
const waitGroupBubbleFlag = 0x8000_0000 // synctest相关
state字段布局:
63..32 31..1 0
+--------------+-----------------------+
| counter | bubble| waiters count |
+--------------+-----------------------+
5.3.1 Add添加计数
go
func (wg *WaitGroup) Add(delta int) {
if race.Enabled {
if delta < 0 {
race.ReleaseMerge(unsafe.Pointer(wg))
}
race.Disable()
defer race.Enable()
}
// 1. 更新计数器: 高32位
state := wg.state.Add(uint64(delta) << 32)
v := int32(state >> 32) // 提取counter
w := uint32(state & 0x7fffffff) // 提取waiters
if race.Enabled && delta > 0 && v == int32(delta) {
race.Read(unsafe.Pointer(&wg.sema))
}
// 2. 检查counter不能为负数
if v < 0 {
panic("sync: negative WaitGroup counter")
}
// 3. 检查并发误用: Add和Wait并发调用
if w != 0 && delta > 0 && v == int32(delta) {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// 4. 正常情况: counter>0 或 无等待者
if v > 0 || w == 0 {
return
}
// 5. counter变为0,有等待者: 唤醒所有等待者
if wg.state.Load() != state {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// 6. 重置状态为0
wg.state.Store(0)
// 7. 唤醒所有等待者
for ; w != 0; w-- {
runtime_Semrelease(&wg.sema, false, 0)
}
}
5.3.2 Wait等待
go
func (wg *WaitGroup) Wait() {
if race.Enabled {
race.Disable()
}
for {
// 1. 读取状态
state := wg.state.Load()
v := int32(state >> 32)
w := uint32(state & 0x7fffffff)
// 2. counter已经为0,直接返回
if v == 0 {
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(wg))
}
return
}
// 3. 增加等待者计数
if wg.state.CompareAndSwap(state, state+1) {
if race.Enabled && w == 0 {
race.Write(unsafe.Pointer(&wg.sema))
}
// 4. 等待信号量
runtime_SemacquireWaitGroup(&wg.sema, false)
// 5. 被唤醒后检查
if wg.state.Load() != 0 {
panic("sync: WaitGroup is reused before previous Wait has returned")
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(wg))
}
return
}
}
}
5.3.3 Done完成
go
func (wg *WaitGroup) Done() {
wg.Add(-1) // 简单调用Add(-1)
}
5.4 sync.Once确保只执行一次
基于 src/sync/once.go 源码:
go
type Once struct {
noCopy noCopy
done atomic.Bool // 是否已执行
m Mutex // 互斥锁
}
Do方法:
go
func (o *Once) Do(f func()) {
// 1. 快速路径: 原子检查是否已执行
if !o.done.Load() {
// 2. 慢速路径
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
// 3. 加锁
o.m.Lock()
defer o.m.Unlock()
// 4. 双重检查(确保并发时只执行一次)
if !o.done.Load() {
// 5. 先执行f,再设置done标志
defer o.done.Store(true)
f()
}
}
为什么用双重检查?
- 第一次检查: 避免每次都获取锁,提高性能
- 加锁: 确保只有一个Goroutine进入
- 第二次检查: 防止多个Goroutine同时通过第一次检查后,重复执行
为什么defer设置done标志?
- 即使f() panic,也会设置done标志
- 确保panic后,后续调用不会再次执行f()
5.5 sync.Map并发安全Map
sync.Map适用于读多写少的场景,核心思想是读写分离:
go
type Map struct {
mu Mutex
// read 包含可以并发安全访问的部分
read atomic.Value // readOnly
// dirty 包含需要加锁访问的部分
dirty map[any]*entry
// misses 记录从read读取miss后,访问dirty的次数
misses int
}
type readOnly struct {
m map[any]*entry
amended bool // dirty包含read中没有的key
}
type entry struct {
p unsafe.Pointer // *interface{}
}
核心机制:
- read: 只读 map,原子访问,用于快速路径
- dirty: 需要加锁访问,包含新写入的 key
- misses: miss 计数,达到阈值时将 dirty 提升为 read
6. Context上下文底层原理(★★★★)
6.1 Context接口
go
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
6.2 Context实现类型
| 类型 | 作用 | 创建方式 |
|---|---|---|
| emptyCtx | 空Context,根Context | Background(), TODO() |
| cancelCtx | 可取消Context | WithCancel() |
| timerCtx | 可超时取消Context | WithDeadline(), WithTimeout() |
| valueCtx | 携带键值对Context | WithValue() |
| withoutCancelCtx | 不可取消Context | WithoutCancel() |
6.3 emptyCtx根Context
go
type emptyCtx struct{}
func (emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (emptyCtx) Done() <-chan struct{} {
return nil
}
func (emptyCtx) Err() error {
return nil
}
func (emptyCtx) Value(key any) any {
return nil
}
var (
background = backgroundCtx{}
todo = todoCtx{}
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
6.4 cancelCtx可取消Context
go
type cancelCtx struct {
Context
mu sync.Mutex // 保护以下字段
done atomic.Value // chan struct{},懒加载
children map[canceler]struct{} // 子Context
err atomic.Value // 取消错误
cause error // 取消原因
}
6.4.1 WithCancel创建可取消Context
go
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := &cancelCtx{}
c.propagateCancel(parent, c)
return c
}
6.4.2 propagateCancel取消传播
go
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
c.Context = parent
// 1. 父Context永远不会取消,直接返回
done := parent.Done()
if done == nil {
return
}
// 2. 父Context已经取消,直接取消子Context
select {
case <-done:
child.cancel(false, parent.Err(), Cause(parent))
return
default:
}
// 3. 父Context也是cancelCtx,加入其children列表
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if err := p.err.Load(); err != nil {
child.cancel(false, err.(error), p.cause)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
return
}
// 4. 其他Context类型,启动Goroutine监听
goroutines.Add(1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
}
6.4.3 cancel取消实现
go
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
if cause == nil {
cause = err
}
c.mu.Lock()
// 1. 检查是否已取消
if c.err.Load() != nil {
c.mu.Unlock()
return // 已取消,直接返回
}
// 2. 设置错误和原因
c.err.Store(err)
c.cause = cause
// 3. 关闭done channel
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
// 4. 递归取消所有子Context
for child := range c.children {
child.cancel(false, err, cause)
}
c.children = nil
c.mu.Unlock()
// 5. 从父Context中移除自己
if removeFromParent {
removeChild(c.Context, c)
}
}
6.5 timerCtx超时Context
go
type timerCtx struct {
cancelCtx
timer *time.Timer // 定时器
deadline time.Time
}
6.5.1 WithDeadline创建超时Context
go
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
return WithDeadlineCause(parent, d, nil)
}
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
// 1. 检查父Context是否有更早的deadline
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
deadline: d,
}
c.cancelCtx.propagateCancel(parent, c)
// 2. 检查deadline是否已过
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, cause)
return c, func() { c.cancel(false, Canceled, nil) }
}
// 3. 设置定时器
c.mu.Lock()
defer c.mu.Unlock()
if c.err.Load() == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, cause)
})
}
return c, func() { c.cancel(true, Canceled, nil) }
}
6.5.2 timerCtx的cancel
go
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
// 1. 调用cancelCtx.cancel取消
c.cancelCtx.cancel(false, err, cause)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
// 2. 停止定时器
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
6.6 valueCtx携带值Context
go
type valueCtx struct {
Context
key, val any
}
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}
// 链式向上查找
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
// ... 其他类型处理
default:
return c.Value(key)
}
}
}
6.7 Context取消传播链路图
#mermaid-svg-VH9v6bnoBmVlHURL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VH9v6bnoBmVlHURL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VH9v6bnoBmVlHURL .error-icon{fill:#552222;}#mermaid-svg-VH9v6bnoBmVlHURL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VH9v6bnoBmVlHURL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VH9v6bnoBmVlHURL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VH9v6bnoBmVlHURL .marker.cross{stroke:#333333;}#mermaid-svg-VH9v6bnoBmVlHURL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VH9v6bnoBmVlHURL p{margin:0;}#mermaid-svg-VH9v6bnoBmVlHURL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VH9v6bnoBmVlHURL .cluster-label text{fill:#333;}#mermaid-svg-VH9v6bnoBmVlHURL .cluster-label span{color:#333;}#mermaid-svg-VH9v6bnoBmVlHURL .cluster-label span p{background-color:transparent;}#mermaid-svg-VH9v6bnoBmVlHURL .label text,#mermaid-svg-VH9v6bnoBmVlHURL span{fill:#333;color:#333;}#mermaid-svg-VH9v6bnoBmVlHURL .node rect,#mermaid-svg-VH9v6bnoBmVlHURL .node circle,#mermaid-svg-VH9v6bnoBmVlHURL .node ellipse,#mermaid-svg-VH9v6bnoBmVlHURL .node polygon,#mermaid-svg-VH9v6bnoBmVlHURL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VH9v6bnoBmVlHURL .rough-node .label text,#mermaid-svg-VH9v6bnoBmVlHURL .node .label text,#mermaid-svg-VH9v6bnoBmVlHURL .image-shape .label,#mermaid-svg-VH9v6bnoBmVlHURL .icon-shape .label{text-anchor:middle;}#mermaid-svg-VH9v6bnoBmVlHURL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VH9v6bnoBmVlHURL .rough-node .label,#mermaid-svg-VH9v6bnoBmVlHURL .node .label,#mermaid-svg-VH9v6bnoBmVlHURL .image-shape .label,#mermaid-svg-VH9v6bnoBmVlHURL .icon-shape .label{text-align:center;}#mermaid-svg-VH9v6bnoBmVlHURL .node.clickable{cursor:pointer;}#mermaid-svg-VH9v6bnoBmVlHURL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VH9v6bnoBmVlHURL .arrowheadPath{fill:#333333;}#mermaid-svg-VH9v6bnoBmVlHURL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VH9v6bnoBmVlHURL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VH9v6bnoBmVlHURL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VH9v6bnoBmVlHURL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VH9v6bnoBmVlHURL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VH9v6bnoBmVlHURL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VH9v6bnoBmVlHURL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VH9v6bnoBmVlHURL .cluster text{fill:#333;}#mermaid-svg-VH9v6bnoBmVlHURL .cluster span{color:#333;}#mermaid-svg-VH9v6bnoBmVlHURL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VH9v6bnoBmVlHURL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VH9v6bnoBmVlHURL rect.text{fill:none;stroke-width:0;}#mermaid-svg-VH9v6bnoBmVlHURL .icon-shape,#mermaid-svg-VH9v6bnoBmVlHURL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VH9v6bnoBmVlHURL .icon-shape p,#mermaid-svg-VH9v6bnoBmVlHURL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VH9v6bnoBmVlHURL .icon-shape .label rect,#mermaid-svg-VH9v6bnoBmVlHURL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VH9v6bnoBmVlHURL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VH9v6bnoBmVlHURL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VH9v6bnoBmVlHURL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WithCancel
WithTimeout
WithCancel
WithValue
WithCancel
rootCtx
(Background)
cancelCtx1
cancelCtx2
timerCtx1
valueCtx1
cancelCtx3
说明:cancelCtx1.cancel()
会递归取消cancelCtx2、valueCtx1、cancelCtx3
7. Goroutine栈管理(★★★★)
7.1 栈初始大小
Go 1.4+: 2KB
Go 1.2-1.3: 8KB
Go 1.0-1.1: 4KB
7.2 栈扩容
#mermaid-svg-YRm6pRZzQTKEWOij{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YRm6pRZzQTKEWOij .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YRm6pRZzQTKEWOij .error-icon{fill:#552222;}#mermaid-svg-YRm6pRZzQTKEWOij .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YRm6pRZzQTKEWOij .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YRm6pRZzQTKEWOij .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YRm6pRZzQTKEWOij .marker.cross{stroke:#333333;}#mermaid-svg-YRm6pRZzQTKEWOij svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YRm6pRZzQTKEWOij p{margin:0;}#mermaid-svg-YRm6pRZzQTKEWOij .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YRm6pRZzQTKEWOij .cluster-label text{fill:#333;}#mermaid-svg-YRm6pRZzQTKEWOij .cluster-label span{color:#333;}#mermaid-svg-YRm6pRZzQTKEWOij .cluster-label span p{background-color:transparent;}#mermaid-svg-YRm6pRZzQTKEWOij .label text,#mermaid-svg-YRm6pRZzQTKEWOij span{fill:#333;color:#333;}#mermaid-svg-YRm6pRZzQTKEWOij .node rect,#mermaid-svg-YRm6pRZzQTKEWOij .node circle,#mermaid-svg-YRm6pRZzQTKEWOij .node ellipse,#mermaid-svg-YRm6pRZzQTKEWOij .node polygon,#mermaid-svg-YRm6pRZzQTKEWOij .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YRm6pRZzQTKEWOij .rough-node .label text,#mermaid-svg-YRm6pRZzQTKEWOij .node .label text,#mermaid-svg-YRm6pRZzQTKEWOij .image-shape .label,#mermaid-svg-YRm6pRZzQTKEWOij .icon-shape .label{text-anchor:middle;}#mermaid-svg-YRm6pRZzQTKEWOij .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YRm6pRZzQTKEWOij .rough-node .label,#mermaid-svg-YRm6pRZzQTKEWOij .node .label,#mermaid-svg-YRm6pRZzQTKEWOij .image-shape .label,#mermaid-svg-YRm6pRZzQTKEWOij .icon-shape .label{text-align:center;}#mermaid-svg-YRm6pRZzQTKEWOij .node.clickable{cursor:pointer;}#mermaid-svg-YRm6pRZzQTKEWOij .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YRm6pRZzQTKEWOij .arrowheadPath{fill:#333333;}#mermaid-svg-YRm6pRZzQTKEWOij .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YRm6pRZzQTKEWOij .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YRm6pRZzQTKEWOij .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YRm6pRZzQTKEWOij .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YRm6pRZzQTKEWOij .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YRm6pRZzQTKEWOij .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YRm6pRZzQTKEWOij .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YRm6pRZzQTKEWOij .cluster text{fill:#333;}#mermaid-svg-YRm6pRZzQTKEWOij .cluster span{color:#333;}#mermaid-svg-YRm6pRZzQTKEWOij div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YRm6pRZzQTKEWOij .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YRm6pRZzQTKEWOij rect.text{fill:none;stroke-width:0;}#mermaid-svg-YRm6pRZzQTKEWOij .icon-shape,#mermaid-svg-YRm6pRZzQTKEWOij .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YRm6pRZzQTKEWOij .icon-shape p,#mermaid-svg-YRm6pRZzQTKEWOij .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YRm6pRZzQTKEWOij .icon-shape .label rect,#mermaid-svg-YRm6pRZzQTKEWOij .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YRm6pRZzQTKEWOij .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YRm6pRZzQTKEWOij .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YRm6pRZzQTKEWOij :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
函数序言检查
栈空间足够?
正常执行
调用morestack
在g0栈上执行newstack
分配新栈(2倍)
copystack拷贝栈内容
调整栈指针
更新G.stack
更新所有指针
返回到用户代码
扩容倍数:
- 小于1KB时: 4倍
- 小于256MB时: 2倍
- 大于等于256MB时: 1.25倍
7.3 栈缩容
GC时检查栈使用情况:
- 如果栈使用少于1/4,且大于2KB,缩容为原来的一半
- 最小缩容到2KB
8. 本地项目源码并发分析
让我们结合前面讲解的原理,分析Go源码中并发相关的实际使用。
8.1 net/http包中的并发使用
net/http是Go中使用并发最多的标准库之一:
go
// src/net/http/server.go
// Serve方法为每个连接启动一个Goroutine
func (srv *Server) Serve(l net.Listener) error {
// ...
for {
rw, err := l.Accept()
// ...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks)
go c.serve(connCtx) // 每个连接一个Goroutine
}
}
func (c *conn) serve(ctx context.Context) {
// ...
for {
w, err := c.readRequest(ctx)
// ...
// 处理请求
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
}
底层原理关联:
- 每个
go c.serve()都会调用newproc创建新G - G会被放入当前P的
runnext或本地队列 - 通过GMP调度器在M上执行
8.2 sync包中的实际应用
让我们看一个标准库中使用Mutex的例子:
go
// src/net/http/server.go
type Server struct {
// ...
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
func (srv *Server) Close() error {
srv.mu.Lock() // 加锁,防止并发修改
defer srv.mu.Unlock()
// ... 关闭listeners和activeConn ...
}
底层原理关联:
srv.mu.Lock()会进入Mutex的Lock流程- 如果有竞争,可能会自旋或进入休眠
defer srv.mu.Unlock()确保解锁
8.3 time包中的Timer与Channel
go
// src/time/sleep.go
func Sleep(d Duration) {
// ...
t := NewTimer(d)
<-t.C // 等待channel
// ...
}
底层原理关联:
- Timer内部维护一个channel
- 超时后向channel发送数据
<-t.C调用chanrecv,如果没有数据会park当前G
9. 面试高频问答(★★★★★)
9.1 GMP调度器专题
Q1: 简单介绍GMP调度器模型?
A:
- G: Goroutine,用户级轻量级线程,包含栈、状态、ID等
- M: Machine,操作系统线程,负责执行G
- P: Processor,处理器资源,包含本地运行队列,数量由GOMAXPROCS决定
调度流程:
- M持有P,从P的本地队列、全局队列、其他P窃取G
- 执行G的代码
- G遇到阻塞(channel、syscall等)时,M与P解绑
- P去寻找其他可运行的G
Q2: 工作窃取(Work Stealing)机制是怎样的?
A:
- 当P的本地队列为空时,会从其他P的本地队列窃取一半的G
- 窃取顺序是随机的,避免饥饿
- 如果所有P都空闲,M会休眠
- 为了保证公平性,每61次调度会检查全局队列
Q3: Goroutine的栈是如何增长的?
A:
- 初始栈大小为2KB
- 函数序言中检查栈空间是否足够
- 如果不足,调用
morestack - 在g0栈上分配新栈(通常是2倍大小)
copystack拷贝旧栈内容到新栈- 更新所有指向旧栈的指针
- 释放旧栈(如果是栈段模式,或延迟释放)
Q4: Goroutine是如何被抢占的?
A:
- 同步抢占 (Go 1.2+): 在函数序言检查栈时,如果
stackguard0 == stackPreempt,触发抢占 - 异步抢占 (Go 1.14+): sysmon监控线程发现G运行超过10ms,发送SIGURG信号,触发
asyncPreempt - 抢占后,G的状态变为
_Gpreempted,放回运行队列
Q5: M0和g0的特殊作用?
A:
- M0 : 程序启动后的第一个M,主线程
- 负责执行runtime初始化
- 启动main goroutine
- g0 : 每个M都有一个g0
- 使用固定大小的系统栈(8KB)
- 执行调度代码、栈扩容、GC等操作
- 不会被抢占调度
Q6: 系统调用时GMP是如何变化的?
A:
- G进入系统调用,状态变为
_Gsyscall - M与P解绑,P状态变为
_Psyscall - sysmon检查到P在系统调用超过20us,将P拿走
- P被其他M获取,执行其他G
- 原M从系统调用返回,尝试获取原来的P
- 如果失败,从空闲P列表获取,或把G放入全局队列
9.2 Channel专题
Q7: 无缓冲和有缓冲Channel的区别?
A:
| 特性 | 无缓冲 | 有缓冲 |
|---|---|---|
| 同步/异步 | 同步,需双方就绪 | 异步,缓冲区可用时不阻塞 |
| len/cap | len=0或1,cap=0 | len=0~cap,cap=n |
| 发送 | 无接收者则阻塞 | 缓冲区未满则不阻塞 |
| 接收 | 无发送者则阻塞 | 缓冲区不空则不阻塞 |
| 适用场景 | 同步通信、信号通知 | 限流、异步处理 |
Q8: 向nil channel发送/接收会怎样?
A:
- 发送: 永久阻塞
- 接收: 永久阻塞
- 关闭: panic
Q9: 向已关闭的channel发送/接收会怎样?
A:
- 发送: panic
- 接收 : 立即返回,如果缓冲区有数据则返回数据,否则返回零值和
ok=false - 关闭: panic
Q10: Channel的收发完整流程是怎样的?
A:
发送:
- 检查是否有等待的接收者,有则直接发送
- 检查缓冲区是否有空位,有则写入缓冲区
- 否则加入发送队列,阻塞等待
接收:
- 检查是否有等待的发送者,有则直接接收
- 检查缓冲区是否有数据,有则读取
- 否则加入接收队列,阻塞等待
Q11: close(ch)之后,所有等待的Goroutine会怎样?
A:
- 所有等待的接收者被唤醒,返回零值和
ok=false - 所有等待的发送者被唤醒,执行
send on closed channelpanic - channel的
closed标志设为1
9.3 sync包专题
Q12: Mutex的正常模式和饥饿模式有什么区别?
A:
| 特性 | 正常模式 | 饥饿模式 |
|---|---|---|
| 锁调度 | 新G可与唤醒的G竞争 | 直接交给队首等待者 |
| 性能 | 高,可连续获取 | 低 |
| 问题 | 可能导致尾延迟 | 解决尾延迟问题 |
| 自旋 | 支持 | 不支持 |
切换条件:
- 等待者等待超过1ms → 进入饥饿模式
- 等待者等待少于1ms,或最后一个等待者 → 退出饥饿模式
Q13: Mutex的state字段各位的含义?
A:
- bit 0: mutexLocked,锁是否被持有
- bit 1: mutexWoken,是否有被唤醒的G
- bit 2: mutexStarving,是否处于饥饿模式
- bit 3~31: 等待者数量
Q14: RWMutex如何实现读写分离?写者饥饿问题如何解决?
A:
实现:
- readerCount: 读者计数,正数表示读者数量,负数表示有写者等待
- writerSem: 写者等待信号量
- readerSem: 读者等待信号量
- w: 互斥锁,保证写操作串行
写饥饿解决:
- 当有写者等待时,新读者会被阻塞(readerCount变为负数)
- 写者等待已存在的读者完成
- 新读者看到readerCount<0会阻塞
Q15: WaitGroup的state字段布局是怎样的?
A:
- 高32位: counter,任务计数器
- 低32位: waiters,等待者数量
Q16: Once如何确保函数只执行一次?
A:
- 快速路径: 原子检查
done标志 - 如果未执行,进入慢速路径
- 加锁
- 双重检查
done标志 - 先执行函数,再设置
done标志(用defer,确保panic也会设置)
Q17: sync.Map适用于什么场景?和直接用Mutex+Map相比有什么优势?
A:
适用场景:
- 读多写少
- 写操作是新增或更新,较少删除
- key集合稳定
优势:
- read部分无锁访问,性能高
- 只有miss时才需要加锁访问dirty
- 避免了大范围锁竞争
9.4 Context专题
Q18: Context的取消传播是如何实现的?
A:
- 每个可取消的Context维护一个
children列表 - 创建子Context时,会将自己加入父Context的children列表
- 当父Context取消时,遍历children列表,递归取消所有子Context
- 同时从父Context的children列表中移除自己
Q19: WithValue传参有什么最佳实践?
A:
- 使用自定义类型作为key,避免和其他包冲突
- key应该是不可导出的
- 只传请求作用域的数据
- 不要传可选参数
- 尽量传小数据
go
// 好的做法
type contextKey string
var userIDKey = contextKey("userID")
func WithUserID(ctx context.Context, userID string) context.Context {
return context.WithValue(ctx, userIDKey, userID)
}
func UserID(ctx context.Context) (string, bool) {
userID, ok := ctx.Value(userIDKey).(string)
return userID, ok
}
Q20: Context为什么应该作为函数第一个参数,而不是放在struct里?
A:
- Context是请求作用域的,不是对象状态的一部分
- 显式传递Context使控制流更清晰
- 方便静态分析工具检查Context传播
- 避免忘记传Context,导致泄漏或取消不生效
9.5 综合题
Q21: Goroutine泄漏的常见场景有哪些?如何避免?
A:
常见场景:
- Channel没有接收者,发送者阻塞
- Channel没有发送者,接收者阻塞
- select中有一个case是nil channel
- Goroutine等待永远不会满足的条件(无超时的mutex等)
- Context没有取消,子Goroutine继续运行
避免方法:
- 使用有缓冲Channel,或确保有接收者
- 使用带超时的select
- 使用Context取消
- 使用WaitGroup确保所有Goroutine完成
- 用go vet等工具检查
Q22: 如何排查Goroutine泄漏?
A:
- 使用
pprof.Lookup("goroutine")获取Goroutine堆栈 - 使用
net/http/pprof的/debug/pprof/goroutine?debug=2查看所有Goroutine堆栈 - 对比不同时间的Goroutine数量,看是否持续增长
- 查看阻塞Goroutine的堆栈,看在等待什么
Q23: CSP模型和共享内存模型有什么区别?Go推荐哪种?
A:
| 特性 | 共享内存模型 | CSP模型 |
|---|---|---|
| 通信方式 | 共享变量+锁 | Channel |
| 数据竞争 | 易发生,需小心 | 不易发生 |
| Go推荐 | 不推荐 | 推荐 |
Go的设计理念:
"不要通过共享内存来通信,而要通过通信来共享内存"
Q24: race detector是如何工作的?
A:
- 编译时插入指令,记录内存访问
- 运行时追踪happens-before关系
- 使用向量时钟检测数据竞争
- 发现竞争时,输出详细的堆栈信息
使用方式:
bash
go test -race
go run -race
Q25: Go的内存模型说的是什么?
A:
Go内存模型定义了在一个Goroutine中对变量的写入,何时能确保被另一个Goroutine看到。
happens-before规则:
- init规则: 包的init函数在main之前执行
- goroutine规则: go语句在goroutine开始前执行
- channel规则:
- 发送happens-before对应的接收完成
- close happens-before接收零值
- mutex规则:
- Unlock happens-before Lock
- waitgroup规则:
- Add happens-before Wait
- Done happens-before Wait返回
10. 踩坑总结与性能优化
10.1 常见踩坑
| 坑 | 原因 | 解决方案 |
|---|---|---|
| Goroutine泄漏 | Channel无收发者、Context未取消 | 使用Context、超时控制 |
| 向已关闭的channel发送 | 忘记检查channel状态 | 确保发送方先关闭,或用sync.Once |
| nil channel操作 | 未初始化channel | 确保channel已make |
| WaitGroup的Add和Wait并发调用 | 违反使用规范 | Add必须在Wait前调用 |
| Mutex未Unlock | 忘记defer | 用defer mu.Unlock() |
| RWMutex读锁升级为写锁 | 设计缺陷 | 不要在RLock时调用Lock |
| Context放在struct里 | 违反使用规范 | Context作为第一个参数显式传递 |
| 拷贝sync包对象 | 包含内部状态 | 用指针传递 |
10.2 性能优化建议
| 优化方向 | 建议 |
|---|---|
| GOMAXPROCS | 通常保持默认,必要时调整 |
| 栈大小 | 避免过大的局部变量 |
| Channel大小 | 合理选择缓冲区大小,避免过大或过小 |
| 锁粒度 | 尽量缩小锁的范围 |
| 读写锁 | 读多写少时用RWMutex |
| sync.Map | 读多写少,key集合稳定时用 |
| Goroutine数量 | 避免过多Goroutine,使用worker pool模式 |
| Context取消 | 及时取消不需要的Context,避免泄漏 |
总结
本文档详细讲解了Go并发编程的核心底层原理:
- GMP调度器 - Goroutine、Machine、Processor的协作
- Channel底层 - hchan结构、收发流程
- sync包原语 - Mutex、RWMutex、WaitGroup、Once等
- Context - 取消传播、超时控制
- Goroutine栈 - 扩容缩容机制
- 面试题 - 覆盖大厂高频考点
- 踩坑与优化 - 实践中常见问题
掌握这些底层原理,不仅能更好地理解Go并发编程,也能在面试中脱颖而出。