Python多进程multiprocessing --- 基于进程的并行

前言

在这里插入图片描述
在Python中,multiprocessing 是一个用于产生进程的包,它具有与用于产生线程的包threading相似的API。 multiprocessing 包同时提供本地和远程并发,使用子进程代替线程,有效避免了Python中GIL锁🔒( Global Interpreter Lock )带来的影响。通过它能充分利用机器上的多核,加快处理速度。

注:最新内容移步官网查看,本文仅对个人使用过程中的一些体会作记录和注解。

multiprocessing核心模块

Process 类

在 multiprocessing 中,一般通过创建一个Process对象然后调用其start()方法来生成进程。在官网中给出的一个简单的多进程程序示例:

from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

Process类的定义如下:

class multiprocessing.Process(group=None, target=None, name=None,
args=(), kwargs={}, *, daemon=None)

需要注意:
Process 类拥有和 threading.Thread 等价的大部分方法。应始终使用关键字参数调用构造函数。 group 应该始终是 None ;它仅用于兼容 threading.Thread 。 target 是由 run() 方法调用的可调用对象。它默认为 None ,意味着什么都没有被调用。 name 是进程名称。 args 是目标调用的参数元组。 kwargs 是目标调用的关键字参数字典。如果提供,则键参数 daemon 将进程 daemon 标志设置为 True 或 False 。如果是 None (默认值),则该标志将从创建的进程继承。默认情况下,不会将任何参数传递给 target 。如果子类重写构造函数,必须确保它在对进程执行任何其他操作之前调用基类构造函数( Process.init() )。

run()

表示进程活动的方法。可以在子类中重载此方法。例如:

class RecordSourceConsumeProcess(Process):
    def __init__(self, record_pid, CPU_use, GPU_use):
        """
        : 用于统计指定pid的进程对系统资源的消耗情况,包括CPU,GPU
        : record_pid 指定进程的pid
        : CPU_use 用于记录CPU消耗的list
        : GPU_use 用于记录GPU消耗的list
        """
        super().__init__()
        self.record_pid = record_pid
        self.CPU_use = CPU_use
        self.GPU_use = GPU_use
    
    def run(self):
        print('对PID: '+str(self.record_pid)+" 的资源消耗统计进程开始运行")
        while True:#除非主进程强行关闭该子进程,否则一直运行不结束
                self.CPU_use.append(self.cpu_record())
                self.GPU_use.append(self.gpu_record())

标准的 run() 方法调用传递给对象构造函数的可调用对象作为目标参数(如果有),分别从 args 和 kwargs 参数中获取顺序和关键字参数。

start()

启动进程活动。这个方法每个进程对象最多只能调用一次。它会将对象的 run() 方法安排在一个单独的进程中调用。

  • 注:经过实际使用发现,如果run()方法未被重载(即标准Process实例),start()后只会启动一个子进程。如果run()方法被重载,start()后会启动两个子进程,且两个子进程具有同一个父进程。当以spawn方式启动多进程时,会发现启动了三个子进程,一个为信号量跟踪进程(multiprocessing.semaphore_tracker.main),另外两个是实际功能的子进程(multiprocessing.spawn.spawn_main)。

只有调用start()后才会真正的启动进程,才能获取到pid和name等信息。

  • 注:需要注意只有start()后才能获取子进程的pid。

join([timeout])

如果可选参数 timeout 是 None (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。一个进程可以被 join 多次。进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。

在主进程中对创建的子进程使用join()方法,在子进程结束前将会阻塞主进程,也就是只有子进程运行结束主进程才能继续运行和结束。

import multiprocessing as mp

def mp_test():
    g = mp.Process(name='test1', target=main_test1)
    h = mp.Process(name='test2', target=main_test2)
    g.start()
    h.start()
    print('g_pid: ',g.pid)#只有调用start()后才会真正的启动进程,才能获取到pid和name等信息
    print('g_name: ',g.name)
    print('h_pid: ',h.pid)
    print('h_name: ',h.name)
    g.join()#如果不调用join方法,mp_test有可能在g和h前结束
    h.join()#但是调用后mp_test必须等g和h结束后才能继续运行和结束
    for i in range(1000*1000*100):
        pass

name

进程的名称。该名称是一个字符串,仅用于识别目的。它没有语义。可以为多个进程指定相同的名称。

初始名称由构造器设定。 如果没有为构造器提供显式名称,则会构造一个形式为 'Process-N1:N2:...:Nk' 的名称,其中每个 Nk 是其父亲的第 N 个孩子。

is_alive()

返回进程是否还活着。

粗略地说,从 start() 方法返回到子进程终止之前,进程对象仍处于活动状态。

if(p.is_alive()):#判断进程p是否还活着
    p.terminate()#p.kill()只有py3.7及以上支持,使用terminate

daemon

进程的守护标志,一个布尔值。这必须在 start() 被调用之前设置。

s = Process(name='test', target=test1, args=(task_flag))
s.daemon = True#开启进程守护,在 start() 被调用之前设置
s.start()

默认为None,初始值继承自创建进程。

注意:

  1. 当某个进程退出时,它会尝试终止其开启的所有守护进程子进程。

  2. 不允许守护进程创建子进程!!!否则,当创建守护进程的主进程(认为是父进程)结束后,守护进程(认为是子进程)也会被关闭,而如果守护进程也创建了子进程,那这些守护进程的子进程(认为是孙子进程)将变成孤儿进程。当尝试这么做时,会报错:AssertionError: daemonic processes are not allowed to have children

在Python 3.3 版中: 加入 daemon 参数。

pid

返回进程ID。在生成该进程(start)之前,这将是 None 。

exitcode

子进程的退出代码。如果进程尚未终止,这将是 None 。负值 -N 表示子进程被信号 N 终止。

authkey

进程的身份验证密钥(字节字符串)。

当 multiprocessing 初始化时,主进程使用 os.urandom() 分配一个随机字符串。

当创建 Process 对象时,它将继承其父进程的身份验证密钥,尽管可以通过将 authkey 设置为另一个字节字符串来更改。

sentinel

系统对象的数字句柄,当进程结束时将变为 “ready” 。

如果要使用 multiprocessing.connection.wait() 一次等待多个事件,可以使用此值。否则调用 join() 更简单。

在Windows上,这是一个操作系统句柄,可以与 WaitForSingleObject 和 WaitForMultipleObjects 系列API调用一起使用。在Unix上,这是一个文件描述符,可以使用来自 select 模块的原语。

Python 3.3 新版功能

terminate()

终止进程。 在Unix上,这是使用 SIGTERM 信号完成的;在Windows上使用 TerminateProcess() 。 请注意,不会执行退出处理程序和finally子句等。

请注意,进程的后代进程将不会被终止 —— 它们将简单地变成孤立的。

警告 如果在关联进程使用管道或队列时使用此方法,则管道或队列可能会损坏,并可能无法被其他进程使用。类似地,如果进程已获得锁或信号量等,则终止它可能导致其他进程死锁。

kill()

terminate() 相同,但在Unix上使用 SIGKILL 信号。

Python 3.7 新版功能,3.7前需要使用terminate()。

close()

关闭 Process 对象,释放与之关联的所有资源。如果底层进程仍在运行,则会引发 ValueError 。一旦 close() 成功返回, Process 对象的大多数其他方法和属性将引发 ValueError 。

Python 3.7 新版功能

启动方法

根据不同的平台, multiprocessing 支持三种启动进程的方法。这些 启动方法有:

spawn

父进程启动一个新的Python解释器进程。子进程只会继承那些运行进程对象的 run() 方法所需的资源。特别是父进程中非必须的文件描述符和句柄不会被继承。相对于使用 fork 或者 forkserver,使用这个方法启动进程相当慢。

可在Unix和Windows上使用。 Windows上的默认设置。

fork

父进程使用 os.fork() 来产生 Python 解释器分叉。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全分叉多线程进程是棘手的。

只存在于Unix。Unix中的默认值。

forkserver

程序启动并选择 forkserver 启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。分叉服务器进程是单线程的,因此使用 os.fork() 是安全的。没有不必要的资源被继承。

可在Unix平台上使用,支持通过Unix管道传递文件描述符。

启动方法注意事项

在 Unix 上通过 spawnforkserver 方式启动多进程会同时启动一个 资源追踪 (resource tracker process)进程,负责追踪当前程序的进程产生的、并且不再被使用的命名系统资源(如命名信号量以及 SharedMemory 对象)。当所有进程退出后,资源追踪会负责释放这些仍被追踪的的对象。

通常情况下是不会有这种对象的,但是假如一个子进程被某个信号杀死,就可能存在这一类资源的“泄露”情况。(泄露的信号量以及共享内存不会被释放,直到下一次系统重启,对于这两类资源来说,这是一个比较大的问题,因为操作系统允许的命名信号量的数量是有限的,而共享内存也会占据主内存的一片空间)

要选择一个启动方法,应该在主模块的 if __name__ == '__main__' 子句中调用 set_start_method() 。在程序中 set_start_method() 不应该被多次调用。

在进程之间交换对象

multiprocessing 支持进程之间的两种通信通道:

队列

Queue 类是一个近似 queue.Queue 的克隆。队列是线程和进程安全的。

管道

Pipe() 函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。

返回的两个连接对象 Pipe() 表示管道的两端。每个连接对象都有 send() 和 recv() 方法(相互之间的)。请注意,如果两个进程(或线程)同时尝试读取或写入管道的同一端,则管道中的数据可能会损坏。当然,在不同进程中同时使用管道的不同端的情况下不存在损坏的风险。

进程间共享状态

在进行并发编程时,通常最好尽量避免使用共享状态。使用多个进程时尤其如此。但是,如果真的需要使用一些共享数据,那么 multiprocessing 提供了两种方法。

共享内存

可以使用 Value 或 Array 将数据存储在共享内存映射中。

服务进程

由 Manager() 返回的管理器对象控制一个服务进程,该进程保存Python对象并允许其他进程使用代理操作它们。

Manager() 返回的管理器支持类型: list 、 dict 、 Namespace 、 Lock 、 RLock 、 Semaphore 、 BoundedSemaphore 、 Condition 、 Event 、 Barrier 、 Queue 、 Value 和 Array 。

main_process_pid = os.getpid()#获取主进程的pid,传入子进程统计资源占用情况
with Manager() as manager:#用Manager封装,利于主进程调用子进程统计的数据
     CPU_use = manager.list()
     GPU_use = manager.list()
     p = RecordSourceConsumeProcess(main_process_pid, CPU_use, GPU_use)
     p.start()#启动子进程
     print('CPU_use: ',CPU_use)
     print('GPU_use: ',GPU_use)

在这里插入图片描述

参考资料

[1] multiprocessing — 基于进程的并行
[2] threading — 基于线程的并行
[3] Python multiprocess模块(上)
[4] Python multiprocess模块(中)
[5] Python multiprocess模块(下)
[6] Python程序中的进程操作-开启多进程
[7] Python 线程

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Python多进程multiprocessing)是一种并行计算的方式,可以在多个CPU核心上同时执行任务,提高程序的运行效率。它可以通过创建多个进程来实现并行计算,每个进程都有自己的独立内存空间,可以独立运行。在Python中,可以使用multiprocessing模块来实现多进程编程,它提供了一系列的类和函数,可以方便地创建和管理多个进程。使用multiprocessing模块可以有效地提高程序的运行速度,特别是在处理大量数据或计算密集型任务时,效果更加明显。 ### 回答2: Python多进程multiprocessing是一个用来创建并行程序的模块,它允许Python程序同时运行多个进程,从而大幅提高程序的运行效率。在Python中,多进程可以通过fork()系统调用实现,但是由于在Windows上无法完全实现fork(),因此Python在Windows上仅支持多线程,而不支持多进程Python多进程中最基本的概念是进程(Process)。在Python中,创建进程有两种基本的方式:使用Process类和使用Pool类。Process类允许我们创建一个进程对象,可以给进程传递参数并启动进程。Pool类则可以创建多个进程,并且自动分配任务到不同的进程中。 Python中还有一些重要的概念和技巧,例如进程间通信(IPC)、共享内存、信号量、锁等。进程间通信是在多进程应用程序中非常常见的技术,它可以让不同的进程之间进行数据交换、资源共享等操作。共享内存则允许不同的进程访问同一个内存区域,从而避免了复制数据的过程,提高了程序的效率。 对于使用Python多进程multiprocessing进行开发的程序,我们需要借助Python标准库中的一些附加模块,例如queue、threading、signal等,以及一些第三方库,例如PyQt、numpy等。对于不同的应用场景,我们可以选择不同的库和技术,从而实现最佳的多进程方案。 总体而言,Python多进程multiprocessing是一个非常强大的模块,可以大幅提高Python程序的运行效率和并发性能。不过,要想熟练地运用这个模块,需要对Python多线程、操作系统和计算机架构等知识有一定的了解。同时,对于更高级别的多进程应用,可能还需要一些专业的领域知识。 ### 回答3: Python是一种面向对象、解释型、高级编程语言,其简洁、易读、易学的语法特点赢得了大批开发者的青睐。而multiprocessing则是Python的一个重要模块,可用于实现多进程并发编程,充分利用计算机多核心资源,提高Python程序的运行效率。 multiprocessing模块的主要特点包括: 1. 提供了Process、Pool、Queue等多个类和函数,方便实现进程的创建、管理和通信。 2. 可以使用fork进程创建新的进程,并可在进程之间共享数据,也可以将任务分配给多个进程执行,提高效率。 3. 提供了多种进程间通信机制的实现,如Pipe、Queue、Value和Array等,让进程之间的通信更加方便,同时保证数据的正确和安全。 4. 灵活的参数设置和控制,比如可以设置进程数、超时时间等,让程序更加可控和稳定。 使用multiprocessing模块可以带来显著的性能提升,特别适合需要处理大规模数据、密集计算或者需要高并发的应用场景。例如,在爬取大规模网站数据时,可以把每个网站的数据抓取任务分配给不同的进程去执行,节约大量时间。在图像处理和机器学习方面也可以大大加快程序的速度。 总之,Pythonmultiprocessing模块为我们提供了一种易用、高效的多进程编程方法,使我们能够更好地利用计算机资源,提高Python程序的性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracelessLe

❀点个赞加个关注再走吧❀

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值