329 lines
8.0 KiB
Markdown
329 lines
8.0 KiB
Markdown
# 第五章:并发编程 —— Goroutine 与 Channel 的艺术(图解版)
|
||
|
||
> **本章目标**:深入理解 Go 并发的核心机制,**配合大量图解**,掌握 Goroutine 调度原理、Channel 通信模式、同步原语等。
|
||
|
||
## 5.1 并发基础与 Goroutine
|
||
|
||
### 5.1.1 并发 vs 并行(图解)
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph 并发 Concurrency
|
||
A[任务 1] -->|交替执行 | B(时间片 1)
|
||
A -->|交替执行 | C(时间片 3)
|
||
D[任务 2] -->|交替执行 | B
|
||
D -->|交替执行 | C
|
||
style A fill:#f9f,stroke:#333
|
||
style D fill:#f9f,stroke:#333
|
||
end
|
||
|
||
subgraph 并行 Parallelism
|
||
E[任务 1] -->|同时执行 | F(CPU 核心 1)
|
||
G[任务 2] -->|同时执行 | H(CPU 核心 2)
|
||
style E fill:#9f9,stroke:#333
|
||
style G fill:#9f9,stroke:#333
|
||
end
|
||
```
|
||
|
||
- **并发**:宏观上同时,微观上**交替**(单核也能实现)。
|
||
- **并行**:微观上**同时**(需要多核 CPU)。
|
||
|
||
---
|
||
|
||
## 5.2 GMP 调度模型 ⭐ 核心重点(图解)
|
||
|
||
Go 的并发性能得益于其独特的 **GMP 调度模型**。
|
||
|
||
### 5.2.1 GMP 模型架构(图解)
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "Go Runtime 用户态调度"
|
||
P1[P1: 逻辑处理器]
|
||
P2[P2: 逻辑处理器]
|
||
P3[P3: 逻辑处理器]
|
||
|
||
Q1[本地 G 队列]
|
||
Q2[本地 G 队列]
|
||
Q3[本地 G 队列]
|
||
|
||
P1 --- Q1
|
||
P2 --- Q2
|
||
P3 --- Q3
|
||
|
||
GlobalQ[全局 G 队列]
|
||
GlobalQ -.-> P1
|
||
GlobalQ -.-> P2
|
||
GlobalQ -.-> P3
|
||
end
|
||
|
||
subgraph "操作系统内核态"
|
||
M1[M1: 线程]
|
||
M2[M2: 线程]
|
||
M3[M3: 线程]
|
||
|
||
M1 <--> P1
|
||
M2 <--> P2
|
||
M3 <--> P3
|
||
end
|
||
|
||
subgraph "Goroutine 实体"
|
||
G1[G1: 协程]
|
||
G2[G2: 协程]
|
||
G3[G3: 协程]
|
||
G4[G4: 协程]
|
||
|
||
G1 --> Q1
|
||
G2 --> Q1
|
||
G3 --> Q2
|
||
G4 --> Q3
|
||
end
|
||
|
||
style P1 fill:#ffeb3b,stroke:#333
|
||
style M1 fill:#2196f3,stroke:#333,color:#fff
|
||
style G1 fill:#4caf50,stroke:#333,color:#fff
|
||
```
|
||
|
||
**图解说明**:
|
||
1. **G (Goroutine)**:绿色的协程,包含栈、指令指针。
|
||
2. **P (Processor)**:黄色的逻辑处理器,管理 G 队列,**数量 = CPU 核数**。
|
||
3. **M (Machine)**:蓝色的操作系统线程,真正执行代码。
|
||
4. **调度**:M 绑定 P,从 P 的本地队列取 G 执行。
|
||
|
||
### 5.2.2 调度流程:从创建到执行
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant App as 应用程序
|
||
participant G as Goroutine
|
||
participant P as P (逻辑处理器)
|
||
participant M as M (线程)
|
||
participant OS as 操作系统
|
||
|
||
App->>P: 创建 G,放入本地队列
|
||
P->>M: M 绑定 P,获取 G
|
||
M->>G: 执行 G 代码
|
||
alt G 阻塞 (如 IO)
|
||
G->>OS: 发起系统调用
|
||
OS-->>M: M 阻塞
|
||
P->>P: 寻找新 M 继续调度其他 G
|
||
Note right of P: P 不阻塞,继续工作!
|
||
else G 完成
|
||
G->>M: 执行完毕
|
||
M->>P: 归还 P
|
||
end
|
||
```
|
||
|
||
### 5.2.3 工作窃取 (Work Stealing)
|
||
|
||
当某个 P 的队列为空时,它会从其他 P 的队列**窃取**一半的 G。
|
||
|
||
```
|
||
P1 队列:[G1, G2, G3, G4, G5] (满载)
|
||
P2 队列:[] (空闲)
|
||
|
||
P2 发现空 -> 向 P1 请求 -> P1 给 G4, G5
|
||
P1 队列:[G1, G2, G3]
|
||
P2 队列:[G4, G5] <-- 窃取成功!
|
||
```
|
||
|
||
---
|
||
|
||
## 5.3 Channel:Goroutine 间的通信(图解)
|
||
|
||
### 5.3.1 无缓冲 Channel (同步)
|
||
|
||
发送和接收必须**同时就绪**,否则阻塞。
|
||
|
||
```
|
||
发送方 (go func) 接收方 (main)
|
||
| |
|
||
| ch <- 42 |
|
||
| (阻塞等待) |
|
||
| <-------------------> | 数据传递 (42)
|
||
| | (同时发生)
|
||
v v
|
||
发送完成 接收完成
|
||
```
|
||
|
||
### 5.3.2 有缓冲 Channel (异步)
|
||
|
||
缓冲区未满时,发送不阻塞;缓冲区非空时,接收不阻塞。
|
||
|
||
```
|
||
缓冲区容量 = 2
|
||
|
||
发送方 缓冲区 接收方
|
||
| [ ] [ ] |
|
||
| ch <- 1 (成功) [1] [ ] |
|
||
| ch <- 2 (成功) [1] [2] |
|
||
| ch <- 3 (阻塞!) [1] [2] | (满了)
|
||
| [1] [2] | <-val (接收 1)
|
||
| [ ] [2] |
|
||
| ch <- 3 (成功!) [3] [2] |
|
||
| [3] [2] | <-val (接收 2)
|
||
| [3] [ ] |
|
||
```
|
||
|
||
### 5.3.3 Channel 状态机
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> 未初始化
|
||
未初始化 --> 打开:make
|
||
打开 --> 打开:发送/接收
|
||
打开 --> 关闭:close()
|
||
关闭 --> 关闭:接收 (返回零值)
|
||
关闭 --> [*]:GC 回收
|
||
打开 --> [*]:GC 回收
|
||
|
||
note right of 打开
|
||
发送阻塞:缓冲区满
|
||
接收阻塞:缓冲区空
|
||
end note
|
||
```
|
||
|
||
---
|
||
|
||
## 5.4 同步原语 (图解)
|
||
|
||
### 5.4.1 WaitGroup 计数原理
|
||
|
||
```
|
||
初始:Counter = 0
|
||
|
||
Goroutine 1: Add(1) -> Counter = 1
|
||
Goroutine 2: Add(1) -> Counter = 2
|
||
Goroutine 3: Add(1) -> Counter = 3
|
||
|
||
Goroutine 1: Done() -> Counter = 2
|
||
Goroutine 2: Done() -> Counter = 1
|
||
Goroutine 3: Done() -> Counter = 0 (唤醒 Wait())
|
||
```
|
||
|
||
### 5.4.2 Mutex 锁状态
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> 空闲:初始
|
||
空闲 --> 占用:Lock()
|
||
占用 --> 空闲:Unlock()
|
||
占用 --> 等待队列:Lock() (阻塞)
|
||
等待队列 --> 空闲:Unlock() (唤醒)
|
||
```
|
||
|
||
### 5.4.3 RWMutex 读写锁
|
||
|
||
```
|
||
读锁 (RLock):允许多个读者同时持有
|
||
写锁 (Lock):排他,只能有一个写者,且不能有读者
|
||
|
||
状态图:
|
||
[空闲] --R--> [多读] --R--> [多读]
|
||
| |
|
||
L L
|
||
v v
|
||
[写] <--------- [多读] (写者等待)
|
||
|
|
||
U
|
||
v
|
||
[空闲]
|
||
```
|
||
|
||
---
|
||
|
||
## 5.5 原子操作 (图解)
|
||
|
||
```
|
||
内存地址:0x1000 (值 = 5)
|
||
|
||
Goroutine 1: atomic.Add(0x1000, 1) -> 硬件级 CAS -> 值 = 6
|
||
Goroutine 2: atomic.Add(0x1000, 1) -> 硬件级 CAS -> 值 = 7
|
||
(无需锁,CPU 指令直接保证原子性)
|
||
```
|
||
|
||
---
|
||
|
||
## 5.6 Context 传递 (图解)
|
||
|
||
```mermaid
|
||
graph LR
|
||
Parent[父 Context] -->|WithTimeout| Child1[子 Context 1]
|
||
Parent -->|WithValue| Child2[子 Context 2]
|
||
|
||
Parent -- 取消 --> Cancel1[取消信号]
|
||
Child1 -- 传播 --> Cancel1
|
||
Child2 -- 传播 --> Cancel1
|
||
|
||
style Parent fill:#ff9800,stroke:#333
|
||
style Cancel1 fill:#f44336,stroke:#333,color:#fff
|
||
```
|
||
|
||
- **取消传播**:父 Context 取消,所有子 Context 自动取消。
|
||
- **超时传播**:父 Context 超时,子 Context 也超时。
|
||
|
||
---
|
||
|
||
## 5.7 竞态检测 (图解)
|
||
|
||
```
|
||
竞态 (Race Condition):
|
||
Goroutine 1: 读 变量 X
|
||
Goroutine 2: 写 变量 X
|
||
(无锁,同时发生) -> 数据不一致!
|
||
|
||
Race Detector 检测:
|
||
Goroutine 1: 读 X (记录时间 T1)
|
||
Goroutine 2: 写 X (记录时间 T2)
|
||
T1 和 T2 重叠 -> 报告竞态!
|
||
```
|
||
|
||
---
|
||
|
||
## 5.8 并发设计模式 (图解)
|
||
|
||
### 5.8.1 管道 (Pipeline)
|
||
|
||
```
|
||
Stage 1 (生成) Stage 2 (平方) Stage 3 (输出)
|
||
[1,2,3] -----> [1,4,9] -----> 打印
|
||
| | |
|
||
(Chan A) (Chan B) (结果)
|
||
```
|
||
|
||
### 5.8.2 工作池 (Worker Pool)
|
||
|
||
```
|
||
任务队列 (100 个)
|
||
|
|
||
v
|
||
+-------------------+
|
||
| Worker 1 (处理) |
|
||
| Worker 2 (处理) | (并发执行)
|
||
| Worker 3 (处理) |
|
||
+-------------------+
|
||
|
|
||
v
|
||
结果队列
|
||
```
|
||
|
||
---
|
||
|
||
*(本章其余部分保持原有文字内容,此处仅展示图解核心)*
|
||
|
||
---
|
||
|
||
## 🎨 图解总结
|
||
|
||
1. **GMP 模型**:P 是调度器,M 是执行者,G 是任务。
|
||
2. **Channel**:无缓冲是同步,有缓冲是异步。
|
||
3. **锁**:Mutex 互斥,RWMutex 读写分离。
|
||
4. **Context**:树状结构,取消信号自顶向下传播。
|
||
5. **管道**:数据流式处理,解耦各阶段。
|
||
|
||
---
|
||
|
||
**代码仓库位置**:https://giter.top/openclaw/test/tree/main/chapters/chapter-5
|
||
|
||
**下一章预告**:HTTP 服务器、路由、中间件、数据库连接池、RESTful API 设计、部署
|