python进阶之系统编程的创建进程和进程进程间通信

来源:互联网 发布:spark 邮箱 windows 编辑:程序博客网 时间:2024/05/16 12:13

python语言中创建进程的方式有fork()、Process(),和进程池三种方法。fork由于不是跨平台的所以在应用中很少,主要以后后两种为主,用得最多的进程池,进程池错做起来相对容易。

一,创建进程:

1、用fork()来创建进程,主要是利用的调用fork()方法后会返回不同的值,一个是0,另一个是大于0的整数,利用这一特点可以进行父子进程的编写,返回值为0代表子进程,大于0的为父进程,父子进程互不干扰,即当父进程结束后子进程仍然可以执行,不会因为父进程的结束而终止子进程的运行,下面的代码例程:

import osimport timeret = os.fork()if ret == 0:while True:print('-------子进程----------')time.sleep(1)else:while True:print('-------父进程----------')time.sleep(1)
至于父子进程的返回值问题,看下面的这个例程:
import osimport timeret = os.fork()print(ret)if ret>0:print('------父进程-----%s'%os.getpid())else:print('------子进程-----%s--%s'%(os.getpid(),os.getppid()))
运行结果是:
6587------父进程-----65860------子进程-----6587--6586
2、Process()创建进程:
如果你打算编写多进程的服务程序,Unix/Linux⽆疑是正确的选择。由于Windows没有fork调⽤,难道在Windows上⽆法⽤Python编写多进程的程序?由于Python是跨平台的,⾃然也应该提供⼀个跨平台的多进程⽀持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了⼀个Process类来代表⼀个进程对象,下⾯的例⼦

演示了启动⼀个⼦进程并等待其结束

from multiprocessing import Processimport timedef test():i=100while True:print('子进程')time.sleep(1)print('子进程:%d'%i)p = Process(target =test)p.start()#同时运行父子进程,也就是开启子进程i=0#用于判断是否存在进程间的变量共享#p.run()#用子进程覆盖父进程while True:print('父进程')time.sleep(1)i += 1print(i)if i==5:p.terminate()#杀死子进程
注意:创建⼦进程时,只需要传⼊⼀个执⾏函数和函数的参数,创建⼀个Process实例,⽤start()⽅法启动,这样创建进程⽐fork()还要简单。join()⽅法可以等待⼦进程结束后再继续往下运⾏,通常⽤于进程间的,换句话说就是,如果不调用join()函数,在主进程结束后,程序就会终止,不管子程序是否执行玩,这一点和fork()不同

Process()的子类创建进程:创建新的进程还能够使⽤类的⽅式,可以⾃定义⼀个类,继承Process类,每次实例化这个类的时候,就等同于实例化⼀个进程对象,请看下⾯的实例:

from multiprocessing import Processimport timeclass sonProcess(Process):def run(self):#重写run方法,将父类中的run方法覆盖,其他方法不变,在父类的run方法中实质上是定义子进程运行的节点#即当调用start()方法时,start方法内会自动调用run方法,并将获取的子进程的运行节点给run 方法,内部在调用run #因为如果只调用run 方法的话,程序会只运行子进程while True:print('子进程的run()')time.sleep(1)p1 = sonProcess()p1.start()#利用继承的思想,p会调用从父类中继承来的start()方法while True:print('父进程')time.sleep(1)

3利用进程池进行子进程的创建:当需要创建的⼦进程数量不多时,可以直接利⽤multiprocessing中的Process动态成⽣多个进程,但如果是上百甚⾄上千个⽬标,⼿动的去创建进程的⼯作量巨⼤,此时就可以⽤到multiprocessing模块提供的Pool⽅法。初始化Pool时,可以指定⼀个最⼤进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建⼀个新的进程⽤来执⾏该请求;但如果池中的进程数已经达到指定的最⼤值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执⾏,请看下⾯的实例:
from multiprocessing import Poolimport timeimport osdef worker(num):for i in range(5):print('----第%d次--pid=%s------num=%d--'%(i,os.getpid(),num))time.sleep(1)pool = Pool(4)for i in range(10):print('=========%d========='%i)pool.apply_async(worker,(i,))#非堵塞式添加任务(常用方式)#pool.apply(worker,(i,))#堵塞式添加任务(极少使用),感觉又又变回了单进程运行程序,只是不用手动启动其他子进程而已,实现了自动的效果pool.close()#关闭进程池,表示不再向进程池中添加进程pool.join()#主进程 创建/添加 进程结束后,主进程默认不会等待子进程结束在终止整个进程的运行,(即不像Process创建进程一样,而是和fork创建进程相似#但是又不尽相同,fork创建的子进程,在父进程结束以后子进程仍然可以单独执行,只是主进程结束了,而Pool进程池在主进程结束后,当即#终止进程池中所有进程)
二、进程间的通信(进程间通信的方式有多种,下面只描述利用队列进行进程间通信):

1、Process():Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递,Queue
本身是⼀个消息列队程序,

说明:初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最⼤可接收
的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到
内存的尽头);
Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之False ;
Queue.full():如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):获取队列中的⼀条消息,然后将其从列队
中移除,block默认值为True;
1)如果block使⽤默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为⽌,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;

2)如果block值为False,消息列队如果为空,则会⽴刻抛出"Queue.Empty"异常;Queue.get_nowait():相当Queue.get(False);Queue.put(item,[block[, timeout]]):将item消息写⼊队列,block默认值为True;
1)如果block使⽤默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写⼊,此时程序将被阻塞(停在写⼊状态),直到从消息列队腾出空间为⽌,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
2)如果block值为False,消息列队如果没有空间可写⼊,则会⽴刻抛出"Queue.Full"异常;Queue.put_nowait(item):相当Queue.put(item, False);

下面是一个利用两个进程,实现读写功能的程序:

#利用队列进行进程间通信,注意队列的数据存储特点,先进先出,(区别栈的特点,先进后出)from multiprocessing import Process,Queueimport os,timedef write(q):for value in ['a','b','c','d']:if not q.full():q.put(value)print('========put>>value:%s=========='%value)time.sleep(1)else:print('写入暂停!')def read(q):while True:if not q.empty():print('========get>>value:%s======='%q.get())time.sleep(1)else:print('读取结束!')breakif __name__=='__main__':q = Queue(3)#q = Queue()pw = Process(target=write,args=(q,))#利用Process()分别创建两个子进程,并返回该进程的pidpr = Process(target=read,args=(q,))pw.start()#通过进程的pid调用start()方法,开启进程pr.start()pr.join()#阻塞进程,避免该进程结束退出程序pw.join()
2、进程池中的Queue如果要使⽤Pool创建进程,就需要使⽤multiprocessing.Manager()中的Queue(),⽽不是multiprocessing.Queue(),否则会得到⼀条如下的错误信息:python基础语⾔进程间通信-Queue 31RuntimeError: Queue objects should only be shared between processesthrough inheritance.下⾯的实例利用进程池实现上面的程序:
#利用队列进行进程池中进程间通信,注意队列的数据存储特点,先进先出,(区别栈的特点,先进后出)from multiprocessing import Pool,Managerimport os,timedef write(q):for value in ['a','b','c','d']:if not q.full():q.put(value)print('========put>>value:%s=========='%value)time.sleep(1)else:print('写入暂停!')def read(q):while True:if not q.empty():print('========get>>value:%s======='%q.get())time.sleep(1)else:print('读取结束!')breakif __name__=='__main__':q = Manager().Queue(3)# 限制队列元素个数#q =Manager().Queue()pool = Pool(3)#pool.apply_async(write,(q,))  #非堵塞式添加进程pool.apply(write,(q,))#堵塞式添加进程#pool.apply_async(read,(q,))pool.apply(read,(q,))pool.close()#关闭进程池,停止添加进程pool.join()#防止主进程结束后随即杀死子进程#进程池创建多进程运行的步骤:#创建进程池对象 pool = Pool() 可以添加限制进程个数#调用apply()或者是zpply_async()添加进程#停止添加进程 pool.close()#防止主进程杀死子进程 pool.join()


补充:进程间通信的方式:

# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。


原创粉丝点击