Skip to main content

Python 进程与线程

Python 进程与进程池

本章节主要讲解 进程是什么? 操作系统基础知识,进程与程序的区别,进程特性,创建单个进程,运行多个进程,进程池(Pool),基本使用:map方法,进程池异步执行,进程与进程池的选择,注意事项与最佳实践。

1. 进程是什么?

想象你的电脑是个超级忙碌的餐厅,CPU 是大厨,内存是食材库,进程就是一道道正在烹饪的菜。每个进程都有自己的“灶台”和“食材”(独立的内存空间),互不干扰,由操作系统这位“餐厅经理”来调度,谁先上菜、谁多占点火力,全靠它指挥。

操作系统基础知识

  • 进程:操作系统分配资源的基本单位,像是餐厅里每张桌子上的订单,包含程序代码、数据和执行状态。
  • 多核 CPU:现代电脑有多个“厨师”(核心),多进程可以让每个核心同时干活,效率翻倍!
  • 进程隔离:每个进程的内存是独立的,进程 A 不会偷看进程 B 的“秘方”,安全又省心。
  • 调度:操作系统像个时间管理大师,用算法(比如时间片轮转)决定哪个进程先跑、跑多久。
  • 线程 vs 进程:线程是进程里的“小帮工”,共享内存,适合 I/O 密集任务;多进程则适合 CPU 密集任务,比如计算密集的数学运算。

Python 的 multiprocessing 模块就是你的“超级助手”,帮你轻松管理多进程,绕过 Python 的全局解释器锁(GIL),让多核 CPU 火力全开!

进程与程序的区别

进程是程序执行时的动态实例,包含独立的内存空间和系统资源,通过系统命令查看进程,在Linux/macOS终端执行: ps aux | grep python ,在Windows CMD执行: tasklist | findstr python

进程特性

  • 独立性:拥有独立地址空间
  • 动态性:生命周期状态(创建/就绪/运行/阻塞/终止)
  • 并发性:现代操作系统通过时间片轮转实现多进程"同时"运行

2. 创建单个进程

multiprocessing.Process类用于创建单个进程。以下是一个基本示例,展示如何启动一个进程并执行任务。

import multiprocessing
import time

def worker(name):
    print(f"进程 {name} 开始执行")
    time.sleep(2)
    print(f"进程 {name} 执行结束")

if __name__ == '__main__':
    process = multiprocessing.Process(target=worker, args=('Worker1',))
    process.start()
    process.join()
    print("主进程结束")
  • target指定进程执行的函数,args传递参数。
  • start()启动进程,join()等待进程完成。
  • if __name__ == '__main__':防止在Windows上重复导入模块。

应用场景:适合简单的并行任务,如单个独立计算或I/O操作。

3. 运行多个进程

当需要同时运行多个任务时,可以创建多个进程。以下示例展示如何并行运行多个进程。

import multiprocessing
import time

def worker(name):
    print(f"进程 {name} 开始执行")
    time.sleep(2)
    print(f"进程 {name} 执行结束")

if __name__ == '__main__':
    processes = []
    for i in range(3):
        p = multiprocessing.Process(target=worker, args=(f"Worker{i+1}",))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print("所有进程执行完毕")
  • 创建多个Process对象,存入列表。
  • 依次启动并等待所有进程完成。

应用场景:适合需要并行执行多个独立任务的场景,如批量数据处理或并行下载。

4. 进程池(Pool)

multiprocessing.Pool用于管理一组进程,自动分配任务到可用进程,适合处理大量任务。进程池通过限制同时运行的进程数量,优化资源使用。

基本使用:map方法

Pool.map()方法将任务分发到进程池,类似于Python内置的map函数。

import multiprocessing

def square(num):
    return num * num

if __name__ == '__main__':
    with multiprocessing.Pool(processes=4) as pool:
        numbers = [1, 2, 3, 4, 5]
        results = pool.map(square, numbers)
    print(f"结果: {results}")
  • Pool(processes=4)创建包含4个进程的进程池。
  • mapsquare函数应用于numbers中的每个元素。
  • with语句确保进程池正确关闭。
  • map 会阻塞主进程,直到所有任务完成并返回结果

应用场景:适合需要对数据列表进行相同操作的场景,如批量计算或数据转换。

5. 进程池异步执行

apply_async允许异步提交任务,返回结果对象,可以稍后获取结果。

import multiprocessing
import time

def process_task(data):
    time.sleep(1)
    return f"处理 {data} 完成"

if __name__ == '__main__':
    with multiprocessing.Pool(processes=3) as pool:
        tasks = [pool.apply_async(process_task, args=(i,)) for i in range(5)]
        results = [task.get() for task in tasks]
    print(f"结果: {results}")

代码说明

  • apply_async异步提交任务,返回ApplyResult对象,不会阻塞主进程,提交后边返回。
  • get()获取任务结果,阻塞直到任务完成。

应用场景:适合需要动态提交任务或处理结果的场景,如实时任务调度。

6. 进程与进程池的选择

  • 使用单个进程
    • 任务数量少,逻辑简单。
    • 需要精细控制进程行为。
    • 示例:单次大型计算任务。
  • 使用进程池
    • 任务数量多,适合批量处理。
    • 需要自动管理进程分配。
    • 示例:大规模数据处理、并行计算。

7. 注意事项与最佳实践

  • 平台兼容性:在Windows上,必须将进程相关代码放在if __name__ == '__main__':中。
  • 资源管理:进程池使用with语句确保资源正确释放。
  • 任务粒度:任务应足够大以抵消进程创建开销,避免过于琐碎的任务。
  • 错误处理:异步任务需检查异常,使用try-except捕获get()可能的错误。
import multiprocessing

def risky_task(num):
    if num == 3:
        raise ValueError("错误示例")
    return num * 2

if __name__ == '__main__':
    with multiprocessing.Pool(processes=2) as pool:
        tasks = [pool.apply_async(risky_task, args=(i,)) for i in range(5)]
        for task in tasks:
            try:
                result = task.get()
                print(f"结果: {result}")
            except Exception as e:
                print(f"捕获错误: {e}")