第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: 进程ID
  • daemon: 是否为守护进程

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 缺点

  • 资源消耗大:每个进程都需要独立的内存空间和系统资源
  • 切换开销大:进程切换需要保存和恢复完整的进程上下文
  • 通信复杂:进程间通信需要使用专门的机制,如管道、队列等
  • 启动速度慢:创建进程的开销比创建线程大

五、多进程编程的适用场景

  1. CPU密集型任务:需要大量计算的任务,如数据处理、科学计算等
  2. 稳定性要求高的任务:不希望一个任务失败影响整个程序
  3. 需要充分利用多核CPU的场景
  4. 需要隔离资源的场景:如运行不可信代码

六、代码示例分析

示例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('主进程执行完毕')

七、练习题目

  1. 创建5个进程,每个进程计算1到1000000的和,并将结果打印出来。

  2. 实现一个守护进程,每隔1秒打印当前时间,当主进程执行3秒后结束,观察守护进程的行为。

  3. 创建一个进程,修改全局变量的值,验证进程间的资源隔离特性。

  4. 使用继承Process类的方式创建进程,实现一个简单的文件复制功能。

  5. 比较多进程和多线程在执行CPU密集型任务(如计算斐波那契数列)时的性能差异。

八、总结

本集我们学习了多进程编程的基础知识,包括:

  • 进程的基本概念和特点
  • 进程与线程的区别
  • Python中使用multiprocessing模块创建和管理进程
  • 进程的生命周期管理
  • 守护进程的概念和使用
  • 进程间的资源隔离特性
  • 多进程编程的优缺点和适用场景

多进程编程是实现并行计算的重要方式,特别适合CPU密集型任务,可以充分利用多核CPU的优势。在实际应用中,我们需要根据任务的特点选择合适的并发编程方式(多进程或多线程)。

下一集我们将学习进程间通信的相关知识,敬请期待!

« 上一篇 线程池 下一篇 » 进程间通信