--- tags: [go, golang, 并发概述, CSP] create time: 2026-06-07 14:30 --- # 并发概述 ## 概述 本文梳理进程、线程、协程的核心概念,区分"并发"与"并行"的本质差异,为后续深入 Go 的并发编程(Goroutine / Channel / Sync)奠定理论基础。 ## 正文 ### 进程与线程 > [!question] 💭 思考 > 一个正在运行的程序,操作系统需要分配哪些资源来支撑它?如果多个程序同时运行,它们之间如何互不干扰? **进程**是操作系统分配资源的基本单位。它是程序在操作系统上的一次执行过程——注意,这里是"执行过程"而非"程序本身",强调的是动态性。 **线程**是 CPU 调度的基本单位,是进程中实际执行任务的实体。 | 维度 | 进程 | 线程 | |------|------|------| | 角色 | 资源分配单位 | CPU 调度单位 | | 开销 | 大(独立地址空间) | 小(共享父进程内存) | | 隔离性 | 强(进程间需 IPC) | 弱(共享同一进程内存) | > [!warning] ⚠️ 常见误区 > "进程 = 重型,线程 = 轻型"——这并不准确。关键区别在于:**进程有独立地址空间,线程共享地址空间**。这也意味着线程间数据共享方便,但更容易出现竞态条件。 ```mermaid graph LR P["进程 Process"] --> T1["线程 Thread 1"] P --> T2["线程 Thread 2"] P --> T3["线程 Thread 3"] style P fill:#e1f5fe ``` 一个进程可以包含多个线程,它们共享堆空间和文件描述符,但各自拥有独立的栈空间和寄存器状态。 ### 协程 > [!question] 💭 思考 > 如果线程的创建和切换需要操作系统参与(陷入内核态),有没有一种更轻量的方式来实现并发? **协程**是用户态线程,由应用程序自己管理调度,不需要操作系统参与。 | 对比项 | 线程 | 协程 | |--------|------|------| | 调度位置 | 内核态(OS 调度) | 用户态(语言运行时调度) | | 切换开销 | 高(涉及上下文切换) | 低(仅保存/恢复栈指针) | | 典型实现 | OS pthread / CreateThread | Go goroutine / Python asyncio | Go 语言的 Goroutine 就是协程的典型实现——它由 Go 运行时(runtime)在自己的 GMP 调度器下管理,而不是直接映射到 OS 线程。 > [!tip] 💡 技巧 > 一个进程可以有多个线程,一个线程可以有多个协程。这意味着协程比线程更轻量:你可以轻松创建数十万个 goroutine,但开数十万个线程几乎必然耗尽系统资源。 ### 并发与并行 > [!question] 💭 思考 > 单核 CPU 上能实现"同时执行"吗?如果能,那"同时"是什么意思? 这两个概念经常被混用,区分的关键只有一点:**能不能真正同时执行**。 - **并行(Parallelism)**:多个任务在同一时刻真正同时在多个核心上执行。必须有多个 CPU 核心支持。 - **并发(Concurrency)**:多个任务交替执行,每个任务执行一小段时间片后让出 CPU,宏观上看起来像"同时"在进行。单核 CPU 也能做到。 ```mermaid timeline title 并行 vs 并发 section 并行 (多核) Task A : 执行 : 执行 : 执行 Task B : 执行 : 执行 : 执行 section 并发 (单核) Task A : 执行1 : 空闲 : 执行3 Task B : 空闲 : 执行2 : 空闲 ``` 如图所示: - **并行**模式下,Task A 和 Task B 在每个时间段都在同时运行——这需要多核 CPU 的支持。 - **并发**模式下,两个任务轮流使用 CPU,每次只执行一小段,交叉进行——这在单核 CPU 上即可完成。 > [!note] 📝 关系总结 > 并行一定是并发(因为并行也满足"交替执行"的广义定义),但并发不一定是并行。Go 的并发模型可以在单核上运行,也可以利用多核实现真正的并行——这取决于 `GOMAXPROCS` 的设置。 ## 关联笔记 - [[hzh/GolangStar/Go语言进阶/Goroutine]] - [[hzh/GolangStar/Go语言原理/gmp调度原理]]