4.0 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-06-07 14:30 |
并发概述
概述
本文梳理进程、线程、协程的核心概念,区分"并发"与"并行"的本质差异,为后续深入 Go 的并发编程(Goroutine / Channel / Sync)奠定理论基础。
正文
进程与线程
[!question] 💭 思考 一个正在运行的程序,操作系统需要分配哪些资源来支撑它?如果多个程序同时运行,它们之间如何互不干扰?
进程是操作系统分配资源的基本单位。它是程序在操作系统上的一次执行过程——注意,这里是"执行过程"而非"程序本身",强调的是动态性。
线程是 CPU 调度的基本单位,是进程中实际执行任务的实体。
| 维度 | 进程 | 线程 |
|---|---|---|
| 角色 | 资源分配单位 | CPU 调度单位 |
| 开销 | 大(独立地址空间) | 小(共享父进程内存) |
| 隔离性 | 强(进程间需 IPC) | 弱(共享同一进程内存) |
[!warning] ⚠️ 常见误区 "进程 = 重型,线程 = 轻型"——这并不准确。关键区别在于:进程有独立地址空间,线程共享地址空间。这也意味着线程间数据共享方便,但更容易出现竞态条件。
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 也能做到。
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的设置。