# 第五章:并发编程 —— 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 设计、部署