Python多进程编程实战:Process、僵尸进程处理与Pool进程池详解

阿澈
阿澈
发布时间:2026-10-13| 5人浏览过
原创

本文详解Python中利用multiprocessing包实现多进程的方法。通过Process类创建子进程,解析group、target、args等参数;针对父进程提前结束导致的僵尸进程问题,对比daemon属性与join()方法的使用效果;介绍通过继承Process类自定义进程的方式,并深入讲解Pool进程池如何限制并发数量以提升CPU利用率,结合代码示例说明异步执行与性能优势。

为了充分释放多核CPU的计算能力,在Python开发中通常必须采用多进程方案。Python标准库提供了multiprocessing模块来支持多进程编程,该模块涵盖了子进程创建、进程间同步与通信机制,并封装了Process、Queue、Pipe、Lock等核心组件。

子进程创建

在multiprocessing模块中,Process类用于实例化具体的进程对象。

Process([group [, target [, name [, args [, kwargs]]]]])

  • group参数用于进程分组,但在实际使用中通常忽略此参数。
  • target参数指定进程启动后调用的可调用对象,通常传入目标函数的名称。
  • args参数以元组形式向target指定的函数传递位置参数。例如,若目标函数a需要接收m和n两个参数,则应设置args=(m, n)。
  • kwargs参数以字典形式向target指定的函数传递关键字参数。
  • name参数用于为进程设置别名,便于识别和管理。

以下是一个基础示例:

# -*- coding:utf-8 -*-
from multiprocessing import Process, Pool
import os
import time


def run_proc(wTime):
  n = 0
  while n < 3:
    print "subProcess %s run," % os.getpid(), "{0}".format(time.ctime())  #获取当前进程号和正在运行是的时间
    time.sleep(wTime)  #等待(休眠)
    n += 1

if __name__ == "__main__":
  p = Process(target=run_proc, args=(2,)) #申请子进程
  p.start()   #运行进程
  print "Parent process run. subProcess is ", p.pid
  print "Parent process end,{0}".format(time.ctime())

运行结果:

Parent process run. subProcess is 30196 Parent process end,Mon Mar 27 11:20:21 2017 subProcess 30196 run, Mon Mar 27 11:20:23 2017 subProcess 30196 run, Mon Mar 27 11:20:25 2017

从上述运行结果可以看出,父进程执行完毕后,子进程仍在继续运行,这种情况可能导致系统产生僵尸(zombie)进程。

正常情况下,子进程结束时会自动通知父进程,释放所占用的内存资源,并在内核中保留其退出状态信息。父进程随后从内核获取该退出信息以完成清理。然而,如果父进程在子进程之前终止,子进程的退出信息将滞留内核,导致子进程变为僵尸进程。当大量僵尸进程累积时,会严重挤占系统内存空间。

如何避免僵尸进程的产生?

这里介绍进程的一个属性deamon。当该属性设置为TRUE时,一旦父进程结束,子进程也会立即终止运行(即使子进程尚未执行完毕)。 因此,在上述程序中添加p.daemon = True,观察其效果。

# -*- coding:utf-8 -*-
from multiprocessing import Process, Pool
import os
import time


def run_proc(wTime):
  n = 0
  while n < 3:
    print "subProcess %s run," % os.getpid(), "{0}".format(time.ctime())
    time.sleep(wTime)
    n += 1

if __name__ == "__main__":
  p = Process(target=run_proc, args=(2,))
  p.daemon = True  #加入daemon
  p.start()
  print "Parent process run. subProcess is ", p.pid
  print "Parent process end,{0}".format(time.ctime())

执行结果:

Parent process run. subProcess is 31856 Parent process end,Mon Mar 27 11:40:10 2017

这引发了新的问题:子进程并未执行完毕,这通常不符合预期需求。是否存在一种方法,确保子进程执行完毕后再让父进程结束?

这里引入p.join()方法。该方法的作用是让父进程阻塞,直到子进程执行结束,父进程才会继续执行后续的代码。

# -*- coding:utf-8 -*-
from multiprocessing import Process, Pool
import os
import time


def run_proc(wTime):
  n = 0
  while n < 3:
    print "subProcess %s run," % os.getpid(), "{0}".format(time.ctime())
    time.sleep(wTime)
    n += 1

if __name__ == "__main__":
  p = Process(target=run_proc, args=(2,))
  p.daemon = True
  p.start()
  p.join()  #加入join方法
  print "Parent process run. subProcess is ", p.pid
  print "Parent process end,{0}".format(time.ctime())

执行结果:

subProcess 32076 run, Mon Mar 27 11:46:07 2017 subProcess 32076 run, Mon Mar 27 11:46:09 2017 subProcess 32076 run, Mon Mar 27 11:46:11 2017 Parent process run. subProcess is 32076 Parent process end,Mon Mar 27 11:46:13 2017

通过这种方式,所有进程均能顺利执行完毕。

将进程定义为类

可以通过继承Process类来定义自定义进程类,并重写run方法。当实例p调用p.start()时,系统会自动调用run方法。

具体实现如下:

# -*- coding:utf-8 -*-
from multiprocessing import Process, Pool
import os
import time


class Myprocess(Process):

  def __init__(self, wTime):
    Process.__init__(self)
    self.wTime = wTime

  def run(self):
    n = 0
    while n < 3:
      print "subProcess %s run," % os.getpid(), "{0}".format(time.ctime())
      time.sleep(self.wTime)
      n += 1


if __name__ == "__main__":
  p = Myprocess(2)
  p.daemon = True
  p.start()  #自动调用run方法
  p.join()
  print "Parent process run. subProcess is ", p.pid
  print "Parent process end,{0}".format(time.ctime())

其执行结果与上一个示例相同。

创建多个进程

在许多场景下,系统需要创建多个进程以提高CPU利用率。当进程数量较少时,可以手动生成Process实例。但当进程数量庞大时,手动管理并发进程数量会显得繁琐。此时,进程池Pool便发挥了重要作用。通过传递参数,可以限制并发进程的数量,默认值通常等于CPU的核数。

直接看示例代码:

# -*- coding:utf-8 -*-
from multiprocessing import Process,Pool
import os,time

def run_proc(name):    ##定义一个函数用于进程调用
  for i in range(5):  
    time.sleep(0.2)  #休眠0.2秒
    print 'Run child process %s (%s)' % (name, os.getpid())
#执行一次该函数共需1秒的时间

if __name__ =='__main__': #执行主进程
  print 'Run the main process (%s).' % (os.getpid())
  mainStart = time.time() #记录主进程开始的时间
  p = Pool(8)      #开辟进程池
  for i in range(16):                 #开辟14个进程
    p.apply_async(run_proc,args=('Process'+str(i),))#每个进程都调用run_proc函数,
                            #args表示给该函数传递的参数。

  print 'Waiting for all subprocesses done ...'
  p.close() #关闭进程池
  p.join() #等待开辟的所有进程执行完后,主进程才继续往下执行
  print 'All subprocesses done'
  mainEnd = time.time() #记录主进程结束时间
  print 'All process ran %0.2f seconds.' % (mainEnd-mainStart) #主进程执行时间

执行结果:

开头部分

Run the main process (30920). Waiting for all subprocesses done … Run child process Process0 (32396) Run child process Process3 (25392) Run child process Process1 (28732) Run child process Process2 (32436)

末尾部分:

Run child process Process15 (25880) All subprocesses done All process last 2.49 seconds.

相关说明:

在此示例中,进程池将并发进程的限制数量设定为8个,而程序总共生成了16个进程。进程池会自动管理系统内的并发进程数量,多余的进程将在队列中等待。限制并发数量的原因是,系统中并发进程并非越多越好;如果并发进程过多,CPU的大部分时间将耗费在进程调度上,而非执行有效计算。

采用多进程并发技术时,对于单个处理器而言,进程的执行是串行的。但在具体时刻,哪个进程获得CPU资源并执行是不可预知的(如执行结果开头部分所示,各进程的执行顺序不固定),这体现了进程的异步特性。

如果串行执行14次run_proc函数,至少需要16秒;而通过进程并发执行,这里仅耗时2.49秒,充分展示了并发技术的优势。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。