python之路-day32-管道、数据共享、进程池

一、管道(不推荐使用,了解即可)

  进程间通信(IPC)方式二:管道(不推荐使用,了解即可),会导致数据不安全的情况出现,后面还会提到为什么

会带来数据不安全的问题。

 1 #创建管道的类:
 2 Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
 3 #参数介绍:
 4 dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。
 5 #主要方法:
 6     conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
 7     conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
 8  #其他方法:
 9 conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
10 conn1.fileno():返回连接使用的整数文件描述符
11 conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。
12  
13 conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
14 conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收    
15  
16 conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
管道介绍
 1 from multiprocessing import Process, Pipe
 2 
 3 def f(conn):
 4     conn.send("Hello 妹妹") #子进程发送了消息
 5     conn.close()
 6 
 7 if __name__ == '__main__':
 8     parent_conn, child_conn = Pipe() #建立管道,拿到管道的两端,双工通信方式,两端都可以收发消息
 9     p = Process(target=f, args=(child_conn,)) #将管道的一段给子进程
10     p.start() #开启子进程
11     print(parent_conn.recv()) #主进程接受了消息
12     p.join()
管道初使用
1 应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起(就是阻塞)。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道的相同一端就会能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点
管道使用注意须知
from multiprocessing import Process, Pipe

def f(parent_conn,child_conn):
    #parent_conn.close() #不写close将不会引发EOFError
    while True:
        try:
            print(child_conn.recv())
        except EOFError:
            child_conn.close()
            break

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(parent_conn,child_conn,))
    p.start()
    child_conn.close()
    parent_conn.send('hello')
    parent_conn.close()
    p.join()            
引发EOFError

主进程将管道的两端都传送给子进程,子进程和主进程共用管道的两种报错情况,都是在recv接收的时候报错的:

    1.主进程和子进程中的管道的相同一端都关闭了,出现EOFError;

    2.如果你管道的一端在主进程和子进程中都关闭了,但是你还用这个关闭的一端去接收消息,那么就会出现OSError;

二、数据共享(了解即可)

  基于消息传递的并发编程是大势所趋

  即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合

  通过消息队列交换数据,这样极大的减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中

注意:进程之间应当是尽量避免通信,即使需要通信,也应该选择进程安全的工具来避免加锁带来的问题。应该尽量避免使用

共享数据的方法,以后会尝试使用数据库来解决进程之间的数据共享问题

  进程之间数据共享模块之一Manager模块:

1 进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
2 虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此
3 
4 A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
5 
6 A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
Manager模块介绍

  多进程共同去处理共享数据的时候,就和我们多进程同时去操作一个文件中的数据一样,不加锁就会出现错误的结果,进程

不安全的,所以也需要加锁

 1 from multiprocessing import Manager,Process,Lock
 2 def work(d,lock):
 3     with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
 4         d['count']-=1
 5 
 6 if __name__ == '__main__':
 7     lock=Lock()
 8     with Manager() as m:
 9         dic=m.dict({'count':100})
10         p_l=[]
11         for i in range(100):
12             p=Process(target=work,args=(dic,lock))
13             p_l.append(p)
14             p.start()
15         for p in p_l:
16             p.join()
17         print(dic)
Manager模块使用

  总结下,进程之间的通信:队列、管道、数据共享

三、进程池

  multiprocess.pool 模块

  创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个

进程去执行使用所有的任务(高级一些的进程池可以根据你的并发量,搞成动态增加或减少进程池中进程数量的操作),

不会开启其他进程,提高操作系统效率,减少空间的占用等

 1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
 2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
 3 initargs:是要传给initializer的参数组
 4 
 5 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
 6 '''需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()'''
 7 
 8 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
 9 '''此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。'''
10     
11 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
12 
13 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
14 
15 方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
16 obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
17 obj.ready():如果调用完成,返回True
18 obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
19 obj.wait([timeout]):等待结果变为可用。
20 obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
Manager的方法
 1 import time
 2 from multiprocessing import Pool,Process
 3 
 4 #针对range(100)这种参数的
 5 # def func(n):
 6 #     for i in range(3):
 7 #         print(n + 1)
 8 
 9 def func(n):
10     print(n)
11     # 结果:
12     #     (1, 2)
13     #     alex
14 def func2(n):
15     for i in range(3):
16         print(n - 1)
17 if __name__ == '__main__':
18     #1.进程池的模式
19     s1 = time.time()  #我们计算一下开多进程和进程池的执行效率
20     poll = Pool(5) #创建含有5个进程的进程池
21     # poll.map(func,range(100)) #异步调用进程,开启100个任务,map自带join的功能
22     poll.map(func,[(1,2),'alex']) #异步调用进程,开启100个任务,map自带join的功能
23     # poll.map(func2,range(100))  #如果想让进程池完成不同的任务,可以直接这样搞
24     #map只限于接收一个可迭代的数据类型参数,列表啊,元祖啊等等,如果想做其他的参数之类的操作,需要用后面我们要学的方法。
25     # t1 = time.time() - s1
26     #
27     # #2.多进程的模式
28     # s2 = time.time()
29     # p_list = []
30     # for i in range(100):
31     #     p = Process(target=func,args=(i,))
32     #     p_list.append(p)
33     #     p.start()
34     # [pp.join() for pp in p_list]
35     # t2 = time.time() - s2
36     #
37     # print('t1>>',t1) #结果:0.5146853923797607s 进程池的效率高
38     # print('t2>>',t2) #结果:12.092015027999878s
进程池的简单应用及与进程池的效率对比

猜你喜欢

转载自www.cnblogs.com/alvin-jie/p/10041017.html