多进程机制与实践
一、核心概念解析
-
进程的本质 进程是操作系统中执行任务的基本单位,代表一个正在运行的程序实例。它由代码、数据、状态和资源组成。在单核处理器上,通过多道技术实现多个进程的交替执行,形成伪并发;而在多核系统中,真正实现并行执行。
-
程序与进程的区别
- 程序:静态的代码集合,存储于磁盘。
- 进程:程序在内存中的动态执行过程,包含运行时状态和资源分配。
- 调度策略
- 先来先服务(FCFS):按请求顺序处理,适合长任务,但不利于短任务响应。
- 短作业优先(SJF):优先调度预计执行时间最短的任务,提高整体效率。
- 时间片轮转(RR):将CPU时间划分为固定片段,每个进程轮流使用,确保公平性。
- 多级反馈队列:设置多个优先级队列,高优先级任务可抢占低优先级任务,动态调整调度策略。
- 并发与并行
- 并发:多个任务在同一时间段内交替执行,用户感知为"同时"进行。
- 并行:多个任务在不同处理单元上同时执行,需多核或分布式硬件支持。
- 单核系统可通过多道技术模拟并发,但无法实现真正的并行。
- 同步与异步、阻塞与非阻塞 | 概念 | 说明 | |------|------| | 同步 | 函数调用等待结果返回后才继续执行 | | 异步 | 发起调用后立即继续后续操作,结果通过回调通知 | | 阻塞 | 当前线程因等待资源而暂停运行 | | 非阻塞 | 不因等待资源而挂起,可继续执行其他逻辑 |
这些术语用于描述任务执行方式和系统行为模式。
二、进程生命周期管理
- 创建方式
- 系统启动时自动创建初始进程
- 用户交互触发(如双击应用)
- 程序内部创建子进程(如PyCharm运行脚本)
- 批处理系统中批量初始化
- 操作系统差异
- Unix/Linux:使用
fork()复制父进程,继承全部上下文。 - Windows:使用
CreateProcess显式创建新进程并加载目标程序。
- 终止类型
- 正常退出:程序主动关闭
- 错误退出:运行时异常导致崩溃
- 严重错误:非法内存访问、除零等致命操作
- 外部强制终止:通过命令如
kill -9强制结束
三、进程状态模型
- 五态模型:创建 → 就绪 → 执行 → 阻塞 → 终止
- 三态模型:运行 → 就绪 → 阻塞
每种状态反映进程当前所处的执行阶段。
四、Python 多进程编程实践
- multiprocessing 模块基础
from multiprocessing import Process
def task(name):
print(f"任务 {name} 在进程 {os.getpid()} 中执行")
time.sleep(2)
print(f"任务 {name} 完成")
if __name__ == '__main__':
# 启动独立进程(并行)
p = Process(target=task, args=("A",))
p.start()
p.join() # 主进程等待完成
关键方法:
start():启动新进程,异步执行join(timeout):阻塞主进程,直到子进程结束is_alive():检查进程是否仍在运行terminate():强制终止进程(不清理资源)
⚠️ 注意:在 Windows 上必须将进程创建代码置于
if __name__ == '__main__':块内,防止无限递归创建。
- 进程创建方式对比
(1)直接调用 Process
p = Process(target=worker, args=(arg,))
p.start()
适用于快速创建并行任务。
(2)继承 Process 类
class Worker(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"{self.name} 开始工作")
time.sleep(1)
print(f"{self.name} 完成")
# 使用
w = Worker("Worker-1")
w.start()
适合需要自定义初始化逻辑或重写行为的情况。
- 内存隔离特性 各进程拥有独立的内存空间,变量修改不会影响其他进程。
money = 100
def increase():
global money
money += 10
p = Process(target=increase)
p.start()
p.join()
print(money) # 仍为 100,因为副本未共享
- 实现高并发服务器
import socket
from multiprocessing import Process
def handle_client(conn):
while True:
data = conn.recv(1024)
if not data: break
conn.send(data.upper())
conn.close()
server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
conn, addr = server.accept()
p = Process(target=handle_client, args=(conn,))
p.start()
每个客户端连接由独立子进程处理,实现真正的并发通信。
- join 方法详解
- 无 join:主进程立即结束,子进程后台运行
- start 后立即 join:串行执行,等待一个完成再启动下一个
- 所有 start 后统一 join:并行执行,总耗时取决于最长任务
processes = [Process(target=work, args=(i,)) for i in range(5)]
for p in processes:
p.start()
for p in processes:
p.join() # 并行等待所有完成
推荐使用此模式以最大化并行效率。
- 进程属性与监控
pid:获取进程唯一标识符ppid:获取父进程编号is_alive():判断是否仍在运行terminate():强制终止进程(注意资源泄漏风险)
示例:
p = Process(target=func)
p.start()
print(p.pid) # 打印子进程ID
print(os.getppid()) # 打印父进程ID
- 调试注意事项
terminate()后is_alive()可能仍返回True,需等待一段时间确认退出。- 调试器可能因进程异常终止而失去单步控制能力,建议避免在调试中频繁使用
terminate()。
五、补充知识
- 进程号查看:
- Windows:
tasklist | findstr PID - Linux:
ps aux | grep PID - 打印顺序混乱原因:多个进程并发输出,不受主进程控制,输出顺序不可预测。