进程
- 在 Python 中,进程(Process) 是操作系统进行资源分配和调度的基本单位,指一个正在运行的 Python 程序实例。每个进程拥有独立的内存空间、文件描述符、全局变量等资源,进程之间相互隔离(默认无法直接共享数据),需通过特定机制(IPC)实现通信。
为什么 Python 需要多进程?
- Python 由于存在GIL(全局解释器锁)的限制:在同一时刻,一个 Python 解释器只能执行一个线程的字节码。这导致多线程在CPU 密集型任务(如大规模计算)中无法真正利用多核 CPU(多线程会因 GIL 切换产生额外开销,效率甚至低于单线程)。
- 而多进程可以绕过 GIL:每个进程拥有独立的 Python 解释器和 GIL,多个进程可同时在不同 CPU 核心上运行,从而真正实现并行计算,提升 CPU 密集型任务的效率。
- Python 中创建进程的核心工具:multiprocessing 模块
僵尸进程和孤儿进程
前言
- 在unix / Linux 系统中 正常情况下 所有的子进程都是由父进程创建的,子进程再创建自己的子进程
- 子进程的结束和父进程的运行是一个异步的过程,也就是说当一个进程运行结束时,它的父进程并不知道,这时候就需要由一个进程来帮助我们完成他工作终止后的收尾工作 , 父进程需要调用 wait 和 waitpid 两个进程来获取到子进程的终止状态
- 子进程死亡的时候万一父进程没有反应过来或者是出现了其他问题,就会导致子进程没人管
僵尸进程
- 僵尸进程就是指子进程死亡之后,父进程没有管他,其它的人也没人管它,就会导致这个子进程的资源没有得到正确的释放从而导致占用资源
- 他会存在于当前的进程列表中并且占用资源,影响到系统的运行
孤儿进程
- 父进程在子进程结束之前就已经结束了,导致子进程与父进程之间的通信消失,这时子进程就会变成一个孤儿进程
- 但是这种孤儿进程会有福利院 (init进程)接管,由当前的 init 进程负责释放当前的资源
孤儿进程和僵尸进程的危害性
- 僵尸进程危害性更大,不会主动关闭子进程 子进程还会占用系统资源
僵尸进程的解决办法
- 自己主动杀死父进程/子进程
- 开启多进程的时候记得join, join会主动回收掉僵尸进程
- 建立一个信号(可以看我的另一篇文章,里面有关于信号的解释)
- 当子进程结束之后向主进程反馈信号,一旦主进程获取到当前的信号就会去主动杀死当前子进程
守护进程
- 守护进程 (daemon) 是在计算机系统启动时就已经运行,并且一直在后台运行的一类特殊进程。
- 它们通常不与用户直接交互,也不接受标准输入和输出,而是在后台执行某种任务或提供某种服务。
- 守护进程往往是由系统管理员手动启动的,它们可以在系统启动时自动启动,一直运行在后台,直到系统关闭或被停止。
- 常见的守护进程包括网络服务 (如 web 服务器、邮件服务器、 ftp服务器等)、日志记录系统 (如系统日志服务、应用程序日志服务等) 等。
- 守护进程通常在后台运行,不需要用户交互,并且有较高的权限,因此编写守护进程需要特别注意安全性和稳定性。
import multiprocessing
import timedef daemon_task():"""守护进程任务:持续打印日志"""while True:print("守护进程运行中...")time.sleep(1)def normal_task():"""普通进程任务:执行3秒后结束"""time.sleep(3)print("普通进程结束")if __name__ == '__main__':# 创建守护进程(daemon=True)daemon_proc = multiprocessing.Process(target=daemon_task)daemon_proc.daemon = True # 标记为守护进程# 创建普通进程normal_proc = multiprocessing.Process(target=normal_task)# 启动进程daemon_proc.start()normal_proc.start()# 主进程等待普通进程结束(3秒后)normal_proc.join()print("主进程结束")# 主进程结束后,守护进程会被自动终止(不会继续打印)# 结果:
# 守护进程运行中...
# 守护进程运行中...
# 守护进程运行中...
# 普通进程结束
# 主进程结束
- 上述代码中,守护进程原本会无限循环打印,但主进程在普通进程结束后退出,守护进程被强制终止,不再输出。
进程间通信
- 在计算机领域,IPC(Inter-Process Communication,进程间通信)是指操作系统中不同进程(运行中的程序)之间交换数据、传递信号或协调行为的技术机制。
- 由于进程在内存中拥有独立的地址空间(为了安全性和隔离性),进程之间不能直接访问彼此的内存,因此需要通过操作系统提供的专门接口实现通信。
- Python 的multiprocessing(队列、管道)模块封装了多种 IPC 工具,简化了进程间数据传递和协同的操作。
管道(Pipe):双向数据传递
- 管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道
- subprocess中Popen执行命令后的 stdout 和stderr 就是管道
- 将消息放入到管道中之后只能从管道中读取一次消息
特点:
适用于两个进程之间的通信(一对一);
数据传递是双向的(全双工);
底层基于操作系统的管道机制,速度较快;
需注意:若两个进程同时读写,可能导致数据混乱,需手动同步(如加锁)。
import multiprocessingdef sender(conn):# 发送数据conn.send("Hello from sender")conn.send(123)conn.close() # 关闭连接def receiver(conn):# 接收数据(recv()会阻塞直到有数据)print(conn.recv()) # 输出:Hello from senderprint(conn.recv()) # 输出:123conn.close()if __name__ == '__main__':# 创建管道,返回两个连接对象conn1, conn2 = multiprocessing.Pipe()# 创建发送和接收进程,分别传入不同的连接对象p_send = multiprocessing.Process(target=sender, args=(conn1,))p_recv = multiprocessing.Process(target=receiver, args=(conn2,))p_send.start()p_recv.start()p_send.join()p_recv.join()
队列(Queue):多进程安全的数据缓冲
- 队列是一个 “先进先出(FIFO)” 的缓冲容器,由multiprocessing.Queue()创建。多个进程可通过put()向队列放入数据,或通过get()从队列取出数据,底层基于管道和锁实现,保证了线程和进程安全。
- 将数据扔到队列中 由另一个进程不断地获取数据即可
特点:
适用于多个进程(多生产者、多消费者)之间的通信;
无需手动处理同步(内部已加锁);
支持阻塞/非阻塞操作(如get(block=False)非阻塞取数据,无数据时抛异常);
JoinableQueue是增强版队列,支持task_done()和join(),可等待队列中所有任务处理完成。
import multiprocessing
import timedef producer(q, name):"""生产者:向队列放数据"""for i in range(3):data = f"{name}的数据{i}"q.put(data)print(f"{name}生产:{data}")time.sleep(0.5) # 模拟耗时def consumer(q, name):"""消费者:从队列取数据"""while True:data = q.get() # 阻塞等待数据if data is None: # 终止信号breakprint(f"{name}消费:{data}")time.sleep(1) # 模拟处理耗时q.task_done() # 通知队列该任务已处理(仅JoinableQueue需要)if __name__ == '__main__':# 创建可等待的队列(适合需要确认任务完成的场景)q = multiprocessing.JoinableQueue()# 创建2个生产者、2个消费者producers = [multiprocessing.Process(target=producer, args=(q, f"生产者{i}"))for i in range(2)]consumers = [multiprocessing.Process(target=consumer, args=(q, f"消费者{i}"))for i in range(2)]# 启动所有进程for p in producers:p.start()for c in consumers:c.start()# 等待所有生产者完成for p in producers:p.join()# 向队列发送终止信号(每个消费者一个)for _ in consumers:q.put(None)# 等待所有消费者处理完数据for c in consumers:c.join()# 等待队列中所有任务完成q.join()print("所有任务处理完毕")
有关信号可以点击阅读另一篇博客。