Python的多线程和多进程是实现并发执行的两种常用方式,二者的核心差异源于Python全局解释器锁(GIL)的存在以及进程线程的底层实现逻辑,理解这些差异才能在实际开发中做出合适的选择。

核心区别对比
我们可以从多个维度对比二者的差异,具体如下表所示:
| 对比维度 | 多线程 | 多进程 |
|---|---|---|
| 底层实现 | 同一进程内多个线程共享进程资源 | 多个独立进程,拥有各自独立的内存空间 |
| GIL影响 | 受GIL限制,同一时刻仅一个线程执行Python字节码 | 每个进程有独立的GIL,不受单个GIL限制 |
| 内存占用 | 内存占用低,线程共享进程内存 | 内存占用高,每个进程独立分配内存 |
| 切换开销 | 切换开销小,仅需保存线程上下文 | 切换开销大,需要切换进程上下文 |
| 数据共享 | 共享全局变量,无需额外通信机制 | 不共享内存,需要通过队列、管道等方式通信 |
| 稳定性 | 一个线程崩溃可能导致整个进程退出 | 进程之间相互独立,一个进程崩溃不影响其他进程 |
多线程的适用场景与示例
多线程适合IO密集型任务,比如网络请求、文件读写、数据库操作等,这类任务的执行过程中大部分时间在等待IO完成,GIL会在IO等待时释放,因此多线程可以提升执行效率。
下面是使用threading模块实现多线程的示例:
import threading
import time
def io_task(task_id):
print(f"任务{task_id}开始执行")
# 模拟IO等待,比如网络请求或者文件读取
time.sleep(2)
print(f"任务{task_id}执行完成")
if __name__ == "__main__":
threads = []
# 创建3个线程执行IO任务
for i in range(3):
t = threading.Thread(target=io_task, args=(i,))
threads.append(t)
t.start()
# 等待所有线程执行完成
for t in threads:
t.join()
print("所有任务执行完毕")
多进程的适用场景与示例
多进程适合CPU密集型任务,比如大量数据计算、图像处理、复杂逻辑运算等,这类任务需要持续占用CPU,多线程受GIL限制无法发挥多核优势,而多进程可以充分利用多核CPU并行计算。
下面是使用multiprocessing模块实现多进程的示例:
import multiprocessing
import time
def cpu_task(task_id):
print(f"任务{task_id}开始执行")
# 模拟CPU密集型计算
result = 0
for i in range(10**7):
result += i
print(f"任务{task_id}执行完成,结果:{result}")
if __name__ == "__main__":
processes = []
# 创建3个进程执行CPU任务
for i in range(3):
p = multiprocessing.Process(target=cpu_task, args=(i,))
processes.append(p)
p.start()
# 等待所有进程执行完成
for p in processes:
p.join()
print("所有任务执行完毕")
如何选择多线程还是多进程
可以按照以下规则判断:
- 如果是IO密集型任务,优先选择多线程,内存占用低、切换开销小,能更好提升IO等待期间的资源利用率。
- 如果是CPU密集型任务,优先选择多进程,可以绕过GIL限制,充分利用多核CPU的计算能力。
- 如果任务需要高稳定性,比如任务崩溃不能影响其他任务执行,优先选择多进程,进程之间相互独立。
- 如果任务需要频繁共享数据,且数据量不大,优先选择多线程,共享全局变量更方便,不需要额外的进程通信成本。
- 如果任务数据量大、通信复杂,优先选择多进程配合队列或者管道通信,避免多线程共享数据带来的锁竞争问题。
注意事项
使用多线程时需要注意线程安全问题,多个线程同时修改共享变量时需要使用threading.Lock加锁,避免数据混乱。使用多进程时需要注意Windows系统下进程代码要放在if __name__ == "__main__":块中,避免子进程重复执行创建逻辑。另外如果任务数量过多,无论是多线程还是多进程都不建议直接创建大量实例,可以配合线程池或者进程池使用,控制并发数量,避免资源耗尽。