Python多任务编程:进程全面详解与实战指南
1. 进程基础概念
1.1 什么是进程?
进程(Process)是指正在执行的程序,是程序执行过程中的一次指令、数据集等的集合。简单来说,进程就是程序的一次执行过程,它是一个动态的概念。
想象你打开电脑上的音乐播放器听歌,同时又在用浏览器上网,这两个就是不同的进程。操作系统会为每个运行的程序创建一个进程,让它们看起来像是同时在运行。
1.2 进程的特征
-  动态性:进程有创建、执行、暂停、终止等生命周期 
-  并发性:多个进程可以同时存在于内存中,在一段时间内交替执行 
-  独立性:每个进程拥有独立的地址空间和系统资源 
-  异步性:进程执行速度不可预知,可能随时被中断 
1.3 进程与程序的区别
| 区别点 | 程序 | 进程 | 
|---|---|---|
| 状态 | 静态的代码集合 | 动态的执行过程 | 
| 生命周期 | 永久保存 | 暂时存在 | 
| 资源占用 | 不占用系统资源 | 占用CPU、内存等资源 | 
2. 进程调度算法
操作系统使用调度算法决定哪个进程可以使用CPU资源:
2.1 先来先服务(FCFS)
-  按照进程到达的顺序执行 
-  简单但不利于短作业 
-  示例:排队买票,先到先得 
processes = ["P1", "P2", "P3"]
for p in processes:print(f"正在执行{p}")2.2 短作业优先(SJF)
-  优先执行预计运行时间短的进程 
-  能减少平均等待时间 
-  但难以准确预估作业长度 
processes = [("P1",3), ("P2",1), ("P3",2)]
processes.sort(key=lambda x: x[1])  # 按执行时间排序2.3 时间片轮转(RR)
-  每个进程分配一个时间片(如100ms) 
-  时间片用完就切换到下一个进程 
-  公平但上下文切换开销大 
from collections import deque
ready_queue = deque(["P1", "P2", "P3"])
time_slice = 1  # 单位时间while ready_queue:p = ready_queue.popleft()print(f"执行{p} {time_slice}单位时间")ready_queue.append(p)  # 重新加入队列2.4 多级反馈队列
-  结合了多种算法的优点 
-  设置多个优先级不同的队列 
-  新进程进入高优先级队列 
-  长时间运行的进程会被移到低优先级队列 
3. 进程的并行与并发
3.1 基本概念
并行(Parallelism):
 指多个任务真正同时执行,需要多核CPU支持。就像餐厅有多个厨师同时做不同的菜。
# 并行示例(假设4核CPU)
from multiprocessing import Pooldef task(n):return n * nif __name__ == '__main__':with Pool(4) as p:  # 创建4个进程print(p.map(task, [1, 2, 3, 4]))  # 4个任务真正同时执行并发(Concurrency):
 指多个任务交替执行,在单核CPU上通过快速切换实现"看似同时"。就像一个厨师轮流做多道菜。
# 并发示例
from multiprocessing import Process
import timedef task(name):print(f"{name}开始")time.sleep(1)print(f"{name}结束")if __name__ == '__main__':processes = []for i in range(3):  # 单核CPU上交替执行p = Process(target=task, args=(f"任务{i}",))p.start()processes.append(p)for p in processes:p.join()3.2 关键区别
| 特性 | 并行 | 并发 | 
|---|---|---|
| 硬件要求 | 需要多核CPU | 单核即可 | 
| 执行方式 | 真正同时执行 | 交替执行 | 
| 效率 | 更高(理想情况) | 相对较低 | 
| 适用场景 | CPU密集型任务 | I/O密集型任务 | 
| 图示 | 🟢🟢🟢(同时进行) | 🟢→🟡→🔴(快速切换) | 
3.3 Python中的实现特点
-  GIL限制:由于全局解释器锁(GIL),Python多线程无法实现真正的并行,多进程是Python实现并行的主要方式 
-  进程开销:进程创建和上下文切换开销比线程大,适合CPU密集型任务 
-  multiprocessing模块:绕过GIL限制,充分利用多核CPU 
4. 同步/异步与阻塞/非阻塞
4.1 进程的三种基本状态

4.2 同步 vs 异步
同步(Synchronous):
-  像排队买奶茶,必须等前一个人完成才能轮到你 
-  代码示例: 
from multiprocessing import Process, Lockdef sync_task(lock, num):with lock:  # 同步锁print(f"进程{num}开始工作")time.sleep(1)print(f"进程{num}结束")if __name__ == '__main__':lock = Lock()for i in range(3):Process(target=sync_task, args=(lock, i)).start()异步(Asynchronous):
-  像取号等餐,拿到号后可以去做其他事 
-  代码示例: 
from multiprocessing import Pooldef async_task(num):print(f"开始异步任务{num}")time.sleep(1)return num * 10if __name__ == '__main__':with Pool(3) as p:results = [p.apply_async(async_task, (i,)) for i in range(3)]for res in results:print(res.get())  # 需要时才获取结果4.3 阻塞 vs 非阻塞
阻塞(Blocking):
-  像打电话订餐,必须等客服回应才能做下一件事 
-  典型表现: join(),get()等方法会阻塞
p = Process(target=time.sleep, args=(5,))
p.start()
p.join()  # 这里主程序会阻塞等待
print("子进程结束")非阻塞(Non-blocking):
-  像发短信订餐,发完就可以做其他事 
-  典型表现:不调用 join()或使用Queue的nowait
processes = []
for i in range(3):p = Process(target=time.sleep, args=(i,))p.start()processes.append(p)# 主进程继续执行其他代码...
print("主进程继续运行")# 最后再统一等待
for p in processes:p.join()4.4 四种组合模式(重点理解)
-  同步阻塞: -  最传统的方式 
-  示例:直接函数调用,等待返回结果 
 result = time.sleep(3) # 同步调用,阻塞等待
-  
-  同步非阻塞: -  轮询检查状态 
-  示例:检查进程是否完成 
 while True:if not p.is_alive():breaktime.sleep(0.1)
-  
-  异步阻塞: -  较少使用 
-  示例:使用回调但主线程等待 
 def callback(result):print("回调结果:", result)with Pool() as pool:res = pool.apply_async(func, args, callback=callback)res.wait() # 这里又变成了阻塞
-  
-  异步非阻塞: -  最高效的方式 
-  示例:使用进程池+回调 
 def callback(result):print("Got result:", result)with Pool() as pool:for i in range(5):pool.apply_async(func=time.sleep,args=(1,),callback=callback)print("主进程继续执行...")pool.close()pool.join()
-  
4.5 实际应用场景建议
| 模式 | 适用场景 | Python实现方式 | 
|---|---|---|
| 同步阻塞 | 简单线性任务 | 直接函数调用 | 
| 同步非阻塞 | 需要轮询的任务 | 循环检查 is_alive() | 
| 异步阻塞 | 较少使用 | apply_async+wait() | 
| 异步非阻塞 | 高并发I/O操作 | 进程池+回调函数 | 
4.6 完整代码示例(综合应用)
"""
多进程模式下载器示例
演示并行、异步非阻塞模式
"""
from multiprocessing import Pool
import time
import randomdef download(url):print(f"开始下载 {url}")time.sleep(random.uniform(1, 3))  # 模拟下载时间print(f"完成下载 {url}")return f"{url}_内容"def save_content(result):print(f"保存结果: {result}")if __name__ == '__main__':urls = ["http://example.com/1","http://example.com/2","http://example.com/3","http://example.com/4"]with Pool(4) as pool:  # 创建进程池# 异步非阻塞模式提交任务results = [pool.apply_async(download, (url,), callback=save_content) for url in urls]print("主进程可以继续处理其他任务...")# 最终等待所有任务完成pool.close()pool.join()print("所有下载任务完成!")这个示例展示了:
-  并行执行(4个下载任务同时进行) 
-  异步非阻塞模式(提交任务后立即继续执行) 
-  回调机制(下载完成后自动保存) 
5. Python中的进程操作
Python通过multiprocessing模块实现多进程编程,下面介绍几种创建进程的方式。
5.1 方式一:使用Process类直接创建
from multiprocessing import Process
import osdef func(num):print(f"这是一个普通方法{num}")print(f"我是子进程,我的pid:{os.getpid()},我的父进程编号:{os.getppid()}")if __name__ == '__main__':# 创建进程对象p1 = Process(name="路飞", target=func, args=(1,))# 启动进程p1.start()# 输出父进程信息print(f"我是父进程,我的pid:{os.getpid()},我的父进程编号:{os.getppid()}")print(p1)代码解析:
-  导入 Process类和os模块
-  定义目标函数 func,它将在子进程中执行
-  创建 Process实例,指定目标函数和参数
-  调用 start()方法启动进程
-  os.getpid()获取当前进程ID,os.getppid()获取父进程ID
5.2 方式二:继承Process类创建
from multiprocessing import Process
import osclass MyProcess(Process):def __init__(self, *args):super(MyProcess, self).__init__()self.args = argsdef run(self):print(f"我是子进程{self.args[0]}")if __name__ == '__main__':p1 = MyProcess(1)p2 = MyProcess(2)p3 = MyProcess(3)p1.start()p2.start()p3.start()代码解析:
-  自定义类继承 Process类
-  重写 run()方法,定义进程执行逻辑
-  创建自定义类的实例并启动 
-  这种方式更面向对象,适合复杂任务 
5.3 进程常用方法
from multiprocessing import Process
import timedef fun():print("我是子进程")for i in range(3):time.sleep(5)print(f"我是子进程{i}")if __name__ == '__main__':p1 = Process(name='路飞', target=fun)p1.start()p1.join()  # 父进程等待子进程结束for i in range(2):time.sleep(1)print(f"我是父进程{i}")关键方法:
-  p.start():启动进程,并调用该子进程中的p.run()
-  p.run (): 进程启动时运行的方法,正是它去调用target 指定的函数,我们自定义类的类中一定要实现该方法
-  p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。 timeout 是可选的超时时间,需要强调的是, p.join 只能 join 住 start 开启的进程,而不能 join 住 run 开启的进程
-  p.is_alive():如果p仍然运行,返回True 
-  p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进 程。使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁 
5.4 进程常用属性
from multiprocessing import Process
import timedef fun():for i in range(10):time.sleep(1)print("我是子进程")if __name__ == '__main__':p = Process(target=fun)p.daemon = True  # 设置为守护进程p.start()time.sleep(5)print("我是父进程")重要属性:
-  daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时, p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置守护进程:跟随着父进程的代码执行结束,守护进程就结束
-  name:进程名称
-  pid:进程ID
-  exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
6. 进程同步与通信
6.1 进程间数据隔离
from multiprocessing import Processdef work():global nn = 0print('子进程内: ', n)if __name__ == '__main__':n = 100p = Process(target=work)p.start()print('主进程内: ', n)输出结果:
主进程内: 100
子进程内: 0解释:进程间内存空间独立,修改子进程中的变量不会影响父进程。
7. 实际应用建议
-  CPU密集型任务:适合使用多进程,可以充分利用多核CPU 
-  I/O密集型任务:多线程可能更合适,避免进程创建开销 
-  守护进程:用于执行后台任务,如日志记录、监控等 
-  进程池:当需要创建大量进程时,考虑使用 Pool类
8. 注意事项
-  Windows平台必须使用 if __name__ == '__main__':保护主代码
-  进程创建和销毁开销较大,不宜创建过多进程 
-  进程间通信需要使用队列(Queue)或管道(Pipe)等机制 
-  避免僵尸进程(子进程结束但父进程未回收资源) 
