学操作系统的时候,进程(Process)和线程(Thread)绝对是绕不开的核心概念。今天我们来聊聊这俩兄弟到底是什么、有啥区别,以及在 Python 里怎么玩多进程和多线程。
什么是进程?
简单来说,进程是操作系统资源分配的基本单位。当你双击打开一个程序(比如 Chrome),操作系统就会为它创建一个进程,分配独立的内存空间、文件描述符等资源。
每个进程都有自己独立的地址空间,进程 A 的内存和进程 B 的内存是完全隔离的。这也意味着进程之间通信(IPC)需要额外的机制,比如 Pipe、Shared Memory、Message Queue 等。
你可以把进程想象成一个个独立的工厂,每个工厂有自己的地盘、设备和原材料,互不干扰。
什么是线程?
线程是 CPU 调度的基本单位,一个进程可以包含多个线程。这些线程共享同一个进程的内存空间、文件描述符等资源,但每个线程有自己的栈(Stack)和程序计数器(Program Counter)。
正因为线程共享内存,所以线程间通信比进程间通信要方便得多——直接读写共享变量就行。但也正因为如此,多线程编程更容易出现 竞态条件(Race Condition),需要用锁(Lock)来保护共享资源。
线程就像工厂里的工人,大家共享同一个车间,但每个人有自己的工位(栈)。如果两个人同时抢一台机器,就会出问题——这就是 Race Condition。
进程 vs 线程:关键区别
- 资源开销:进程开销大(独立地址空间),线程开销小(共享地址空间)
- 创建速度:进程创建慢,线程创建快
- 通信方式:进程间需要 IPC 机制,线程间直接读写共享内存
- 隔离性:进程相互隔离(一个崩溃不影响其他),线程共享内存(一个出问题可能影响整个进程)
- 调度:线程是 CPU 调度的基本单位,进程是资源分配的基本单位
并发 vs 并行
这两个概念经常被混淆,但其实不一样:
- Concurrency(并发):多个任务在逻辑上同时执行,可能是交替运行的(单核 CPU 也能并发)
- Parallelism(并行):多个任务在物理上同时执行,需要多核 CPU
打个比方:一个厨师同时炒菜、煮汤、蒸饭,他交替操作这叫并发;三个厨师各负责一道菜,这叫并行。
Python 中的多线程:threading
Python 通过 threading 模块提供多线程支持。来看个简单例子:
import threading
import time
def worker(name):
print(f"线程 {name} 开始工作")
time.sleep(2)
print(f"线程 {name} 完成工作")
# 创建两个线程
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))
t1.start()
t2.start()
t1.join()
t2.join()
print("所有线程完成")
GIL:Python 多线程的”紧箍咒”
学 Python 多线程,你一定会遇到 GIL(Global Interpreter Lock)。这是 CPython 解释器的一个全局锁,它保证同一时刻只有一个线程在执行 Python 字节码。
这意味着:Python 的多线程不适合 CPU 密集型任务(比如大量计算),因为 GIL 让多线程无法真正并行。但对于 I/O 密集型任务(比如网络请求、文件读写),多线程还是很有效的,因为 I/O 等待时会释放 GIL。
Python 中的多进程:multiprocessing
如果你需要真正的并行计算,就该用 multiprocessing 模块了。它会创建独立的进程,每个进程有自己的 Python 解释器,不受 GIL 限制:
from multiprocessing import Process, Pool
import time
def cpu_heavy(n):
"""CPU 密集型任务"""
total = 0
for i in range(n):
total += i * i
return total
if __name__ == "__main__":
# 方式1:手动创建进程
start = time.time()
p1 = Process(target=cpu_heavy, args=(5_000_000,))
p2 = Process(target=cpu_heavy, args=(5_000_000,))
p1.start(); p2.start()
p1.join(); p2.join()
print(f"多进程耗时: {time.time() - start:.2f}s")
# 方式2:使用进程池(推荐)
start = time.time()
with Pool(4) as pool:
results = pool.map(cpu_heavy, [5_000_000] * 4)
print(f"进程池耗时: {time.time() - start:.2f}s")
什么时候用多线程,什么时候用多进程?
- I/O 密集型(网络请求、文件读写、数据库查询)→ 用
threading或asyncio - CPU 密集型(数值计算、图像处理、机器学习训练)→ 用
multiprocessing - 如果不想手动管理线程/进程,可以考虑
concurrent.futures的ThreadPoolExecutor和ProcessPoolExecutor
线程安全与死锁
多线程编程中最常见的两个坑:
1. Race Condition(竞态条件)
import threading
counter = 0
lock = threading.Lock()
def unsafe_increment():
global counter
for _ in range(100000):
counter += 1 # 不安全!读-改-写不是原子操作
def safe_increment():
global counter
for _ in range(100000):
with lock: # 用锁保护
counter += 1
2. Deadlock(死锁)
当两个线程各自持有对方需要的锁,又在等待对方释放,就会陷入死锁。经典场景:
# 危险!可能导致死锁
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread1():
with lock_a:
time.sleep(0.1)
with lock_b: # 等待 lock_b,但 lock_b 被 thread2 持有
pass
def thread2():
with lock_b:
time.sleep(0.1)
with lock_a: # 等待 lock_a,但 lock_a 被 thread1 持有
pass
避免死锁的方法:按固定顺序获取锁、使用 threading.Lock 的超时机制、或者用 concurrent.futures 等高层抽象。
总结
- 进程是资源分配单位,线程是 CPU 调度单位
- 进程隔离但开销大,线程共享但需要同步
- Python 因为 GIL 的存在,CPU 密集型任务用多进程,I/O 密集型用多线程
- 多线程编程要注意 Race Condition 和 Deadlock
理解进程和线程是学习操作系统的基础,也是写高性能程序的前提。建议大家动手跑一下上面的代码,感受多线程和多进程的差异。有问题欢迎留言讨论 👋