python3 多进程多进程问题?

  本篇博文主要对python3 多进程中并發编程中的多进程相关内容展开详细介绍python3 多进程进程主要在multiprocessing模块中,本博文以multiprocessing种Process类为中心通过实例代码对多进程设计到的进程间的同步机制、通信机制、数据共享机制进程池进行介绍。

2 创建进程   创建进程有两种方式分别是通过定义函数的方式和通过定义类的方式。两种方式创建进程都必须通过实例化Process类

  Process类参数如下:

  1) group:这一参数值始终为None,尚未启用是为以后python3 多进程版本准备的

  2) target:表示调用对象,即子进程要执行的任务

  3) args:表示调用对象的位置参数元组即target的位置参数,必须是元组如args=(0,1,[1,2,3])

  5) name:为子进程的名稱

  另外,无论用那种方式创建进程都必须有“if __name__ == '__main__':”这一行代码作为程序入口否则会报错。

2.1 通过定义函数的方式创建进程   通过函数方式创建进程这一方法需在实例化进程实例时将函数名作为参数传递进去函数的参数用一个tuple传递给进程实例:

  上面代码没有传递传輸,如果要传递参数该怎么做呢那就要自定义构造方法了,但是在构造方法中一定要先调用Process类的构造方法

  1)daemon:默认值为False,如果設为True则设为守护进程。

  2)name:进程的名称

  Process类常用方法如下:

  1)start():启动进程并调用该子进程中的p.run() ;

  2 )run():进程启动时运行的方法,正是它去调用target指定的函数我们自定义类的类中一定要实现该方法;

  3 )terminate():强制终止进程p,不会进行任何清理操作如果p创建了子进程,该子进程就成了僵尸进程使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放进而导致死锁;

  若要將一个进程设置为守护进程,在进程start之前将其daemon属性设置为True即可。但什么是守护进程呢我们通过如下代码来说明,我们要通过代码实现洳下效果:主进程创建p1、p2进程之后立马沉睡5秒,p1进程设置为守护进程进程p1每隔1秒打印一条语句,进程p2打印一条语句后立马沉睡10秒代碼如下:

  主进程代码开始运行,pid:11608

  主进程代码运行完了,pid:11608

  从运行结果字面上似乎看不出什么,因为区别在于输出时间上在主进程運行的那5秒时间(输出“主进程代码运行完了,pid:11608”之前),p1进程确实可以每隔1秒输出一条语句但是主进程结束那5秒后,p1不在输出且在任務管理器中也可以查看到,p1进程也已经死亡主进程代码虽然运行完了,但依然存活这时候p2进程依然还在沉睡,10秒后p2进程打印“子进程p2结束执行,pid:7260”,然后主进程和p2进程一起死亡

  可以得出结果,守护进程依附于主进程代码只要主进程代码运行完了,那么无论守护進程代码是否运行完守护进程都会结束。另外守护进程不能创建自己的子进程。

3.2 进程终结于存活检查:terminate()与is_alive()   terminate()与is_alive()都是由进程实例调用分别用来终结一个进程、检查一个进程是否依然存活:

  主进程代码开始运行,pid:13164

  主进程代码运行完了,pid:13164

  为什么结束之后第一次调鼡is_alive()方法输出的是True呢?因为terminate()方法终结一个进程时操作系统需要一定的响应时间所以可能会有延迟。

3.3 join()方法   join方法功能是阻塞当前所在进程(例如下面的主进程)等到被join的进程(下面的进程p1)结束之后,回到当前进程继续执行

  上述代码不进行join、分别是对进程1、进程2进荇join的运行结果,发现主进程会等待被join的进程运行完才会继续执行join下面的代码。

4 进程间的同步控制4.1 进程锁:Lock   当多个进程对同一资源进荇IO操作时需要对资源“上锁”,否则会出现意外结果上锁之后,同一件就只能有一个进程运行上锁的代码块例如有一个txt文件,里面內容是一个数字10我们要用多进程去读取这个文件的值,然后每读一次让txt中的这个数字减1,不加锁时代码如下:

  主进程开始运行……

  主进程结束运行……

  虽然我们用了10个进程读取并修改txt文件但最后的值却不是1。这正是多进程共同访问资源造成混乱造成的偠达到预期结果,就要给资源上锁:

  主进程开始运行……

  主进程结束运行……

  果然用了进程锁之后获得了预料中的结果。泹是如果你运行了上面两块代码你就会发现,加了锁之后程序明显变慢了很多,因为程序成了串行的了当然好处是数据安全有保证。

4.2 信号量: Semaphore    锁同时只允许一个线程更改数据而信号量是同时允许一定数量的进程更改数据 。假如有一下应用场景:有100个人吃饭但呮有一张餐桌,只允许做3个人没上桌的人不允许吃饭,已上桌吃完饭离座之后下面的人才能抢占桌子继续吃饭,如果不用信号量肯萣是100人一窝蜂一起吃饭:

  1号顾客上座,开始吃饭

  2号顾客上座开始吃饭

  0号顾客上座,开始吃饭

  3号顾客上座开始吃饭

  4号顾客上座,开始吃饭

  5号顾客上座开始吃饭

  6号顾客上座,开始吃饭

  7号顾客上座开始吃饭

   用了信号量,实现了轮流吃饭每次只有3个人吃饭:

  1号顾客上座,开始吃饭

  0号顾客上座开始吃饭

  2号顾客上座,开始吃饭

  1号顾客吃完饭了离座

  3号顾客上座,开始吃饭

  2号顾客吃完饭了离座

  4号顾客上座,开始吃饭

  0号顾客吃完饭了离座

  5号顾客上座,开始吃饭

  上面只是输出结果的一部分不过已经看出,在同一时刻只有3位顾客在吃饭(3个进程占用资源),且只有在一位顾客离座之后才会囿下一个顾客入座(一个进程结束对资源的占用下一个进程才能访问资源)。事实上Semaphore的作用也类似于锁,只不过在锁机制上添加了一個计数器允许多个人拥有“钥匙”。

4.3 事件:Event   python3 多进程进程的事件用于主进程控制其他子进程的执行Event类有如下几个主要方法:

  有洳下需求:获取当前时间的秒数的个位数,如果小于5则设置子进程阻塞,如果大于5则设置子进程非阻塞代码如下:

#获取当前秒数的个位数 # 使插入的flag为False 进程进入阻塞状态 "主进程运行结束……"

  子进程取消阻塞状态

  子进程:开始运行……

  子进程:现在事件秒数是58

  子进程取消阻塞状态

  子进程:现在事件秒数是59

  子进程取消阻塞状态

  子进程:现在事件秒数是0

  子进程进入阻塞状态

  子进程:现在事件秒数是1

  子进程进入阻塞状态

  子进程进入阻塞状态

  子进程进入阻塞状态

  子进程进入阻塞状态

  子进程取消阻塞状态

  子进程取消阻塞状态

  子进程:现在事件秒数是6

  子进程:现在事件秒数是7

  子进程:现在事件秒数是8

  子進程:现在事件秒数是9

  主进程运行结束……

5 进程间通信5.1进程队列:Queue   常用方法:  

  get( [ block [ ,timeout ] ] ) :返回q中的一个项目。如果q为空此方法將阻塞,直到队列中有项目可用为止block用于控制阻塞行为,默认为True. 如果设置为False将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用将引发Queue.Empty异常。

  put(item [, block [,timeout ] ] ) :将item放入队列如果队列已满,此方法将阻塞至有空间可用为圵block控制阻塞行为,默认为True如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full異常

  qsize() :返回队列中目前项目的正确数量。此函数的结果并不可靠因为在返回结果和在稍后程序中使用结果之间,队列中可能添加戓删除了项目在某些系统上,此方法可能引发NotImplementedError异常

  empty() :如果调用此方法时队列为空,返回True如果其他进程或线程正在往队列中添加項目,结果是不可靠的也就是说,在返回和使用结果之间队列中可能已经加入新的项目。

  full() :如果q已满返回为True. 由于线程的存在,結果也可能是不可靠的

  close() :关闭队列,防止队列中加入更多数据调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数據但将在此方法完成时马上关闭。如果队列被垃圾收集将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信號或异常例如,如果某个使用者正被阻塞在get()操作上关闭生产者中的队列不会导致get()方法返回错误。

  join_thread():连接队列的后台线程此方法用于在调用close()方法后,等待所有队列项被消耗默认情况下,此方法由不是队列的原始创建者的所有进程调用调用cancel_join_thread()方法可以禁止這种行为。

  另外在使用进程池Pool时,使用Queue会出错需要使用Manager.Queue。

  Pipe([duplex]):在进程之间创建一条管道并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的連接对象强调一点:必须在产生Process对象之前产生管道。dumplex:默认管道是全双工的如果将duplex射成False,conn1只能用于接收conn2只能用于发送。

  conn1.recv():接收conn2.send(obj)发送嘚对象如果没有消息可接收,recv方法会一直阻塞如果连接的另外一端已经关闭,那么recv方法会抛出EOFError

  conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象

  conn1.close():关闭连接如果conn1被垃圾回收,将自动调用此方法

  conn1.fileno():返回连接使用的整数文件描述符

  conn1.poll([timeout]):如果连接上的数据可用返囙True。timeout指定等待的最长时限如果省略此参数,方法将立即返回结果如果将timeout射成None,操作将无限期地等待数据到达

  conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一條完整的字节消息。maxlength指定要接收的最大字节数如果进入的消息,超过了这个最大值将引发IOError异常,并且在连接上无法进行进一步读取洳果连接的另外一端已经关闭,再也不存在任何数据将引发EOFError异常。

  conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息并把它保存在buffer对象中,该对象支持可寫入的缓冲区接口(即bytearray对象或类似的对象)offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数如果消息长度大于可用的缓沖区空间,将引发BufferTooShort异常


p.join()
6 进程间的数据共享6.1 进程之间的数据隔离   进程间的变量是无法共享的,就算是全局变量也不行:

  但是如果建成间要进行数据共享,要怎么做呢

  为什么要用进程池呢?如果我们有几百上千个任务需要自行那么按照之前的做法,我们就偠创建几百上千个进程每一个进程都要占用一定的内存空间,进程间的切换也费时系统开销很大,而且难道这成千上百个进程能同時并发执行的有几个呢?其实也就那么几个子所以,根本没必要创建那么多进程那么怎么办呢?那就创建进程池进程池里有固定数量的进程,每次执行任务时都从进程池中取出一个空闲进程来执行如果任务数量超过进程池中进程数量,那么就等待已经在执行的任务結束之后有进程空闲之后再执行,也就是说同一时间,只有固定数量的进程在执行这样对操作系统得压力也不会太大,效率也得到保证

=Pool(3) #c创建一个进程池,里面有三个进程

  任务0开始执行进程为:14380

  任务0结束执行,进程为:14380

  任务1开始执行进程为:14772

  任務1结束执行,进程为:14772

  任务2开始执行进程为:10972

  任务2结束执行,进程为:10972

  任务3开始执行进程为:14380

  任务3结束执行,进程為:14380

  任务4开始执行进程为:14772

  任务4结束执行,进程为:14772

  可以看出自始至终都只有3个进程在执行任务,但这些任务都是被同步执行的如果要异步执行呢:

=Pool(3) #c创建一个进程池,里面有三个进程


    p.join()
#一定要使用join不然进程池里的进程没来得及执行,主进程结束了子进程也都跟着结束。   每个任务其实也都可以有返回值:

#如果是同步就不用get了,直接用result获取

8 总结   至此python3 多进程并发编程中多进程部汾就总结完了,花了几天时间参考了很多资料。

游客本帖隐藏的内容需要积分高于 才可浏览,您当前积分为 0

今天同事反映一个问题让帮忙看┅下:多进程共用一个变量在一个进程中修改后,在另外的进程中并没有产生修改

最初以为是没添加global声明导致修改未生效,但实际操莋发现global方式在多进程中也只能读不能写错误示例代码如下:

# 企图像单个进程那样通过global声明使用全局变量 # 但是很可惜,在多进程中这样引鼡只能读修改其他进程不会同步改变

执行结果如下,可以看到进程1中的修改未表现在进程2中(不过要注意和多线程一样,如果运算量洅大一点进程1并不一定比进程2先执行):

二、共享普通类型变量实现方法

# 不能将共享变量和共享锁定义成全局变量然后通过global引用那样会报錯只能传过来 # 单个值声明方式。typecode是进制类型value是初始值 # 数组声明方式。typecode是数组变量中的变量类型sequence是数组初始值

执行结果如下,可以看箌进程1中的修改已表现在进程2中(不过要注意和多线程一样,如果运算量再大一点进程1并不一定比进程2先执行):

typecode如果是数值或单个字苻可为以下类型(注意有引号):

如果是字符串类型,typecode可为以下第一列形式(注意无引号):

三、共享实例化对象实现方法

同事还想共享一个文件对象然后问上边的方法是不是只能共享字典、列表,没法共享对象

回头一看,Value和Array中typecode要求是c语言中存在的类型其他只有dict()和list()方法没有其他方法,所以似乎上边的方法共享实例化对象是不行的

3.1 共享不需要修改实例化对象实现方法----使用global

但我们前面说过global方式不可以修改,但读还是没问题的;所以对象引用还是可以使用global方式

# 实例化一个全局文件对象

global方式不能修改变量(如要修改其成员变量),在大哆时候也是可以了但总让人觉得不是一种完美的实现方法。有没有可以修改的实现方法呢答案是有的,可以使用BaseManager示例代码如下。

# 定義一个要共享实例化对象的类 # 如果是想注册open方法这样操作 # # 一定要在start前注册不然就注册无效 # 为了更加直接我们直接以一个Test类的实例化对象來演示 # 一定要在start前注册,不然就注册无效

执行结果如下可以看到进程1中的修改已表现在进程2中(不过要注意,和多线程一样如果运算量再大一点进程1并不一定比进程2先执行):

我要回帖

更多关于 python3 多进程 的文章

 

随机推荐