第118集 多进程编程基础
学习目标
- 理解进程的基本概念
- 掌握进程与线程的区别
- 学会使用Python的
multiprocessing模块创建和管理进程 - 了解进程间的资源隔离特性
- 掌握进程的生命周期管理
- 理解多进程编程的优缺点
一、进程的基本概念
1.1 什么是进程?
进程(Process)是操作系统进行资源分配和调度的基本单位,是程序的一次执行过程。每个进程都有自己独立的内存空间、文件描述符、环境变量等资源。
1.2 进程的特点
- 独立性:每个进程都有自己独立的内存空间和系统资源
- 并发性:多个进程可以同时运行,通过操作系统的调度实现
- 动态性:进程是程序的一次执行过程,有创建、执行、终止等生命周期
- 异步性:进程的执行是异步的,无法预知执行顺序
二、进程与线程的区别
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 系统资源的基本单位 | CPU调度的基本单位 |
| 内存空间 | 独立的内存空间 | 共享所属进程的内存空间 |
| 通信方式 | 需要使用进程间通信机制 | 可以直接共享内存 |
| 切换开销 | 较大,需要保存和恢复完整的进程上下文 | 较小,只需要保存和恢复线程上下文 |
| 稳定性 | 一个进程崩溃不会影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
| 多核利用 | 天然支持多核并行执行 | 在Python中由于GIL限制,同一时刻只能有一个线程执行 |
三、Python中的多进程编程
Python提供了multiprocessing模块来支持多进程编程。这个模块提供了类似threading模块的API,使我们可以方便地创建和管理进程。
3.1 创建进程的两种方式
3.1.1 使用Process类创建进程
from multiprocessing import Process
import time
def worker(name):
"""子进程要执行的任务"""
print(f'子进程 {name} 开始执行')
time.sleep(2)
print(f'子进程 {name} 执行完毕')
if __name__ == '__main__':
print('主进程开始执行')
# 创建进程对象
p1 = Process(target=worker, args=('p1',))
p2 = Process(target=worker, args=('p2',))
# 启动进程
p1.start()
p2.start()
# 等待进程结束
p1.join()
p2.join()
print('主进程执行完毕')3.1.2 继承Process类创建进程
from multiprocessing import Process
import time
class MyProcess(Process):
"""自定义进程类"""
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
"""进程要执行的任务"""
print(f'子进程 {self.name} 开始执行')
time.sleep(2)
print(f'子进程 {self.name} 执行完毕')
if __name__ == '__main__':
print('主进程开始执行')
# 创建进程对象
p1 = MyProcess('p1')
p2 = MyProcess('p2')
# 启动进程
p1.start()
p2.start()
# 等待进程结束
p1.join()
p2.join()
print('主进程执行完毕')3.2 进程的重要方法和属性
3.2.1 常用方法
start(): 启动进程,调用进程的run()方法run(): 进程要执行的任务,一般需要重写join([timeout]): 等待进程结束,可选超时时间is_alive(): 检查进程是否正在运行terminate(): 强制终止进程kill(): 与terminate()类似,在Unix系统上发送SIGKILL信号,Windows系统上与terminate()相同
3.2.2 常用属性
name: 进程名称pid: 进程IDdaemon: 是否为守护进程
3.3 守护进程
守护进程(Daemon Process)是在后台运行的进程,当主进程结束时,守护进程也会自动结束。
from multiprocessing import Process
import time
def daemon_worker():
while True:
print('守护进程正在运行...')
time.sleep(1)
def normal_worker():
print('普通进程开始执行')
time.sleep(3)
print('普通进程执行完毕')
if __name__ == '__main__':
print('主进程开始执行')
# 创建守护进程
daemon_p = Process(target=daemon_worker)
daemon_p.daemon = True # 设置为守护进程
# 创建普通进程
normal_p = Process(target=normal_worker)
# 启动进程
daemon_p.start()
normal_p.start()
# 等待普通进程结束
normal_p.join()
print('主进程执行完毕')
# 主进程结束后,守护进程自动结束3.4 进程间的资源隔离
每个进程都有自己独立的内存空间,进程之间的变量是相互隔离的。
from multiprocessing import Process
shared_var = 0
def worker():
global shared_var
shared_var = 100
print(f'子进程中的shared_var: {shared_var}')
if __name__ == '__main__':
print(f'主进程中的shared_var: {shared_var}')
p = Process(target=worker)
p.start()
p.join()
print(f'主进程中的shared_var: {shared_var}') # 仍然是0,因为进程间资源隔离四、多进程编程的优缺点
4.1 优点
- 充分利用多核CPU:每个进程可以运行在不同的CPU核心上,实现真正的并行计算
- 稳定性高:一个进程崩溃不会影响其他进程
- 避免GIL限制:由于每个进程都有自己的Python解释器和GIL,因此可以充分利用多核CPU
4.2 缺点
- 资源消耗大:每个进程都需要独立的内存空间和系统资源
- 切换开销大:进程切换需要保存和恢复完整的进程上下文
- 通信复杂:进程间通信需要使用专门的机制,如管道、队列等
- 启动速度慢:创建进程的开销比创建线程大
五、多进程编程的适用场景
- CPU密集型任务:需要大量计算的任务,如数据处理、科学计算等
- 稳定性要求高的任务:不希望一个任务失败影响整个程序
- 需要充分利用多核CPU的场景
- 需要隔离资源的场景:如运行不可信代码
六、代码示例分析
示例1:基本进程创建和管理
from multiprocessing import Process
import time
import os
def worker(name):
"""子进程要执行的任务"""
print(f'子进程 {name} 启动,PID: {os.getpid()}, 父进程PID: {os.getppid()}')
time.sleep(2)
print(f'子进程 {name} 结束')
if __name__ == '__main__':
print(f'主进程启动,PID: {os.getpid()}')
# 创建进程对象
processes = []
for i in range(3):
p = Process(target=worker, args=(f'p{i}',))
processes.append(p)
# 启动所有进程
for p in processes:
p.start()
# 等待所有进程结束
for p in processes:
p.join()
print('所有子进程执行完毕,主进程结束')示例2:进程的生命周期管理
from multiprocessing import Process
import time
def worker():
print('子进程开始执行')
time.sleep(5)
print('子进程执行完毕')
if __name__ == '__main__':
print('主进程开始执行')
p = Process(target=worker)
print(f'进程创建后状态: {p.is_alive()}')
p.start()
print(f'进程启动后状态: {p.is_alive()}')
print(f'进程ID: {p.pid}')
print(f'进程名称: {p.name}')
# 等待2秒后检查进程状态
time.sleep(2)
print(f'2秒后进程状态: {p.is_alive()}')
# 终止进程
p.terminate()
time.sleep(0.1) # 等待终止操作完成
print(f'终止后进程状态: {p.is_alive()}')
p.join()
print('主进程执行完毕')七、练习题目
创建5个进程,每个进程计算1到1000000的和,并将结果打印出来。
实现一个守护进程,每隔1秒打印当前时间,当主进程执行3秒后结束,观察守护进程的行为。
创建一个进程,修改全局变量的值,验证进程间的资源隔离特性。
使用继承
Process类的方式创建进程,实现一个简单的文件复制功能。比较多进程和多线程在执行CPU密集型任务(如计算斐波那契数列)时的性能差异。
八、总结
本集我们学习了多进程编程的基础知识,包括:
- 进程的基本概念和特点
- 进程与线程的区别
- Python中使用
multiprocessing模块创建和管理进程 - 进程的生命周期管理
- 守护进程的概念和使用
- 进程间的资源隔离特性
- 多进程编程的优缺点和适用场景
多进程编程是实现并行计算的重要方式,特别适合CPU密集型任务,可以充分利用多核CPU的优势。在实际应用中,我们需要根据任务的特点选择合适的并发编程方式(多进程或多线程)。
下一集我们将学习进程间通信的相关知识,敬请期待!