进程、线程、协程:让你一次搞懂的通俗指南
进程、线程、协程:让你一次搞懂的通俗指南
作为程序员,我们经常听到进程、线程、协程这些概念,但它们到底是什么?有什么区别?今天我们用最通俗的方式来聊聊这三个概念。
先说个生活中的例子
想象你在经营一家餐厅:
- 进程就像整个餐厅,有自己的厨房、餐具、食材、员工
- 线程就像餐厅里的厨师,多个厨师共享同一个厨房和食材
- 协程就像一个厨师在做多道菜,炖汤的时候去炒菜,炒菜等油热的时候去切菜
一、进程(Process):独立的王国
什么是进程?
进程是程序运行的一个实例,是操作系统分配资源的基本单位。每个进程都有自己独立的内存空间,就像每个餐厅都有自己的厨房。
进程的特点
- 独立性强:每个进程有自己的地盘,互不干扰
- 资源开销大:开一家新餐厅要租场地、买设备,很贵
- 通信麻烦:两家餐厅要交流,得打电话或派人跑腿
实际例子
当你打开电脑时:
- Chrome 浏览器是一个进程
- 微信是另一个进程
- VS Code 又是一个进程
它们各自运行,互不影响。Chrome 崩溃了,微信还能正常用。
# Python 创建多进程的例子
from multiprocessing import Process
import os
def worker(name):
print(f'工作进程 {name},进程ID: {os.getpid()}')
print(f'父进程ID: {os.getppid()}')
if __name__ == '__main__':
print(f'主进程ID: {os.getpid()}')
# 创建两个进程
p1 = Process(target=worker, args=('A',))
p2 = Process(target=worker, args=('B',))
p1.start()
p2.start()
p1.join()
p2.join()
print('所有进程执行完毕')
输出示例:
主进程ID: 12345
工作进程 A,进程ID: 12346
父进程ID: 12345
工作进程 B,进程ID: 12347
父进程ID: 12345
所有进程执行完毕
二、线程(Thread):共享资源的工人
什么是线程?
线程是进程内的执行单元,是 CPU 调度的基本单位。一个进程可以有多个线程,它们共享进程的资源。
线程的特点
- 共享内存:同一餐厅的厨师共享厨房和食材
- 切换快速:厨师之间换班比开新餐厅快多了
- 互相影响:一个厨师打翻油锅,整个厨房都遭殃
实际例子
Chrome 浏览器这个进程里:
- 一个线程负责渲染页面
- 一个线程负责处理用户输入
- 一个线程负责网络请求
- 多个线程负责执行 JavaScript
# Python 创建多线程的例子
import threading
import time
def download_file(file_name):
print(f'开始下载 {file_name}')
time.sleep(2) # 模拟下载耗时
print(f'{file_name} 下载完成')
# 创建多个线程同时下载
threads = []
files = ['电影.mp4', '音乐.mp3', '文档.pdf', '图片.jpg']
for file in files:
t = threading.Thread(target=download_file, args=(file,))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print('所有文件下载完成!')
输出:
开始下载 电影.mp4
开始下载 音乐.mp3
开始下载 文档.pdf
开始下载 图片.jpg
电影.mp4 下载完成
音乐.mp3 下载完成
文档.pdf 下载完成
图片.jpg 下载完成
所有文件下载完成!
注意:四个文件几乎同时开始,同时结束,总耗时约 2 秒,而不是 8 秒。
三、协程(Coroutine):聪明的多任务
什么是协程?
协程是一种用户态的轻量级线程,由程序自己控制调度。就像一个厨师在等水开的时候去切菜,充分利用等待时间。
协程的特点
- 极其轻量:创建成千上万个协程毫无压力
- 主动让出:程序自己决定什么时候切换
- 适合 I/O:特别适合网络请求、文件读写等需要等待的任务
实际例子
爬虫程序:
- 发送请求后要等服务器响应
- 等待期间可以去处理其他请求
- 不需要创建大量线程
# Python asyncio 协程示例
import asyncio
import time
async def cook_soup():
print('开始煮汤')
await asyncio.sleep(3) # 模拟煮汤需要 3 秒
print('汤煮好了')
return '一碗汤'
async def cook_rice():
print('开始煮饭')
await asyncio.sleep(2) # 模拟煮饭需要 2 秒
print('饭煮好了')
return '一碗饭'
async def stir_fry():
print('开始炒菜')
await asyncio.sleep(1) # 模拟炒菜需要 1 秒
print('菜炒好了')
return '一盘菜'
async def prepare_dinner():
print('开始准备晚餐...\n')
start = time.time()
# 同时进行三个任务
results = await asyncio.gather(
cook_soup(),
cook_rice(),
stir_fry()
)
end = time.time()
print(f'\n晚餐准备完毕:{results}')
print(f'总耗时:{end - start:.1f} 秒')
# 运行协程
asyncio.run(prepare_dinner())
输出:
开始准备晚餐...
开始煮汤
开始煮饭
开始炒菜
菜炒好了
饭煮好了
汤煮好了
晚餐准备完毕:['一碗汤', '一碗饭', '一盘菜']
总耗时:3.0 秒
如果按顺序做,需要 3 + 2 + 1 = 6 秒。用协程并发执行,只需 3 秒!
四、三者对比
形象比喻
| 概念 | 比喻 | 特点 |
|---|---|---|
| 进程 | 不同的餐厅 | 独立运营,互不干扰,开销大 |
| 线程 | 餐厅里的厨师 | 共享厨房,协作工作,开销中等 |
| 协程 | 一个厨师做多道菜 | 充分利用等待时间,开销极小 |
技术对比
| 特性 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 调度者 | 操作系统内核 | 操作系统内核 | 用户程序 |
| 切换开销 | 大(毫秒级) | 中(微秒级) | 小(纳秒级) |
| 创建数量 | 几十到几百 | 几百到几千 | 几千到几万 |
| 内存占用 | 独立(几 MB) | 共享(几百 KB) | 共享(几 KB) |
| 并行执行 | ✅ 真并行 | ✅ 真并行 | ❌ 并发非并行 |
| 适用场景 | CPU 密集型 | CPU 密集型 | I/O 密集型 |
五、什么时候用什么?
用进程的场景
- 隔离性要求高:比如浏览器的每个标签页,一个崩溃不影响其他
- 多核 CPU 利用:科学计算、视频转码等 CPU 密集任务
- 不同程序语言:不同模块用不同语言编写
# 计算密集型任务用多进程
from multiprocessing import Pool
def calculate_heavy(n):
return sum(i * i for i in range(n))
if __name__ == '__main__':
with Pool(4) as p: # 4 个进程
results = p.map(calculate_heavy, [10000000] * 4)
print(f'计算完成:{results}')
用线程的场景
- 共享数据多:需要频繁访问共享变量
- 响应式 UI:界面程序,主线程处理 UI,其他线程处理任务
- 中等并发:不需要成千上万的并发连接
// Java 下载管理器
public class DownloadManager {
public void downloadFiles(List<String> urls) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (String url : urls) {
executor.submit(() -> {
downloadFile(url);
});
}
executor.shutdown();
}
}
用协程的场景
- 高并发 I/O:Web 服务器、爬虫、聊天服务器
- 网络请求密集:API 调用、数据库查询
- 资源受限:不能创建太多线程的场景
// Node.js Web 服务器(天然支持协程)
const express = require('express');
const app = express();
app.get('/user/:id', async (req, res) => {
// 并发查询数据库和缓存
const [user, cache] = await Promise.all([
db.query('SELECT * FROM users WHERE id = ?', req.params.id),
redis.get(`user:${req.params.id}`)
]);
res.json(user || cache);
});
app.listen(3000); // 可以同时处理成千上万个请求
六、实战案例:爬虫对比
同样是爬取 100 个网页,看看三种方式的差别:
1. 单线程同步(最慢)
import requests
import time
def fetch_sync(urls):
results = []
for url in urls:
response = requests.get(url)
results.append(response.text)
return results
# 耗时约:100 秒(假设每个请求 1 秒)
2. 多线程(快)
from concurrent.futures import ThreadPoolExecutor
def fetch_threaded(urls):
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(requests.get, urls))
return results
# 耗时约:10 秒(10 个线程并发)
3. 协程(最快)
import asyncio
import aiohttp
async def fetch_async(urls):
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [await r.text() for r in responses]
# 耗时约:1-2 秒(可以创建 100 个协程同时请求)
七、常见误区
误区 1:线程越多越快?
❌ 错! 线程太多会导致频繁的上下文切换,反而降低性能。通常线程数应该接近 CPU 核心数(CPU 密集型)或稍多一些(I/O 密集型)。
误区 2:协程可以利用多核?
❌ 错! 单个线程内的协程无法利用多核。如果要利用多核,需要多进程 + 协程的组合。
误区 3:进程一定比线程安全?
⚠️ 不完全对! 进程间通信如果使用共享内存,同样有并发问题。
总结
- 进程:独立的房子,隔离性好,开销大,适合需要隔离或多核计算的场景
- 线程:共享的房间,通信方便,开销中等,适合需要共享数据的并发任务
- 协程:聪明的多任务,轻量高效,开销极小,适合高并发 I/O 密集型任务
记住这个选择公式:
- CPU 密集 + 多核 → 多进程
- 共享数据多 → 多线程
- 高并发 I/O → 协程
希望这篇文章能帮你彻底理解这三个概念!如果还有疑问,欢迎留言讨论。
最后提醒:实际项目中,这三者常常组合使用。比如 Web 服务器可能是:多进程(利用多核)+ 每个进程内用协程(处理高并发请求)。
后记
2026年8月10日于上海,在claude opus 4.8辅助下完成。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)