35.管道,进程池

管道

 

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

#创建管道的类:
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异常。
conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收    
 
conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。

应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起(就是阻塞)。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道的相同一端就会能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。

 

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

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

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

 

    所以你关闭管道的时候,就容易出现问题,需要将所有只用这个管道的进程中的两端全部关闭才行。当然也可以通过异常捕获(try:except EOFerror)来处理。

    虽然我们在主进程和子进程中都打印了一下conn1一端的对象,发现两个不再同一个地址,但是子进程中的管道和主进程中的管道还是可以通信的,因为管道是同一套,系统能够记录。    

 

    我们的目的就是关闭所有的管道,那么主进程和子进程进行通信的时候,可以给子进程传管道的一端就够了,并且用我们之前学到的,信息发送完之后,再发送一个结束信号None,那么你收到的消息为None的时候直接结束接收或者说结束循环,就不用每次都关闭各个进程中的管道了。

 

from multiprocessing import Process,Pipe
# conn1,conn2 = Pipe()
# conn1.send('你好啊')
# print('>>>>>>>>>>')
# msg = conn2.recv()
# print(msg)



#
# def func1(conn2):
#     msg = conn2.recv()
#     print('>>>',msg)
#         #如果管道的一端关闭了,那么另外一端在接收消息的时候就会报错
#     msg2 =conn2.recv()   #没有信息接受的时候会报错
# if __name__ == '__main__':
#     conn1,conn2 =Pipe()
#     p = Process(target = func1,args =(conn2,))
#     p.start()
#     conn1.send('小瘪犊子')
#     conn1.close()


#
# #解决方案,抛异常
# def func1(conn2):
#     try :
#         msg = conn2.recv()
#         print('>>>',msg)
#         #如果管道的一端关闭了,那么另外一端在接收消息的时候就会报错
#         msg2 =conn2.recv()   #没有信息接受的时候会报错
#     except EOFError:
#         print('对方的管道一段已经关闭')
#         conn2.close()
# if __name__ == '__main__':
#     conn1,conn2 =Pipe()
#     p = Process(target = func1,args =(conn2,))
#     p.start()
#     conn1.send('小瘪犊子')
#     conn1.close()


# def func(conn1,conn2):
#     try :
#         msg = conn2.recv()
#         print('>>>',msg)
#         msg2 = conn2.recv()
#     except EOFError:
#         print('对方的管道已经关闭')
#         conn2.close
# if __name__ == '__main__':
#     conn1,conn2 =  Pipe()
#     p =Process(target = func,args =(conn1,conn2,))
#     p.start()
#     conn1.send('小婊砸')
#     conn1.close()
#     # conn1.recv()#错误信息  OSError: handle is closed

#
# def func(conn1,conn2):
#     msg = conn2.recv()#阻塞
#
# if __name__ == '__main__':
#     conn1,conn2 =  Pipe()
#     p =Process(target=func,args = (conn1,conn2,))
#     p.start()
   #不发消息的时候,会在func中阻塞住

 管道可以用于双工通信,通常利用在客户端/服务端中使用的请求/响应模型,或者远程过程调用,就可以使用管道编写与进程交互的程序,像前面将网络通信的时候,我们使用了一个叫subprocess的模块,里面有个参数是pipe管道,执行系统指令,并通过管道获取结果。

 数据共享

展望未来,基于消息传递的并发编程是大势所趋

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

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

    进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题,应该尽量避免使用本节所讲的共享数据的方式,以后我们会尝试使用数据库来解决进程之间的数据共享问题。

from multiprocessing import Process,Manager

def func(m_dic):
    m_dic['name'] = '啥蹩犊子玩意儿'

if __name__ == '__main__':
    m = Manager()
    m_dic = m.dict({'name':'小瘪三'})
    print('主进程的',m_dic)
    p = Process(target=func,args = (m_dic,))
    p.start()
    p.join()

    print('主进程',m_dic)

数据共享的不安全

 

from multiprocessing import Process,Manager,Lock
def func(m_dic,ml):
    #不加锁的时候会出现数据错乱
    m_dic['count'] -= 1
    #加锁
    # with ml:
    #     m_dic['count'] -= 1

    #加锁效果等同于
    # ml.acquire()
    # m_dic['count'] -= 1
    # ml.release()

if __name__ == '__main__':
    m = Manager()
    ml = Lock()
    m_dic = m.dict({'count':100})
    p_list =[]
    for i in range(20):
        p1 =Process(target=func,args = (m_dic,ml,))
        p1.start()
        p_list.append(p1)
    [p.join() for p in p_list]#列表推导式

    print('主进程',m_dic)

 

信号量和事件也相当于锁,也是全局的,所有进程都能拿到这些锁的状态,进程之间这些锁啊信号量啊事件啊等等的通信,其实底层还是socekt,只不过是基于文件的socket通信,而不是跟上面的数据共享啊空间共享啊之类的机制,我们之前学的是基于网络的socket通信,还记得socket的两个家族吗,一个文件的一个网络的,所以将来如果说这些锁之类的报错,可能你看到的就是类似于socket的错误,简单知道一下就可以啦~~~

工作中常用的是锁,信号量和事件不常用,但是信号量和事件面试的时候会问到,你能知道就行啦~~~

 

进程池

 在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程(空间,变量,文件信息等等的内容)也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,维护一个很大的进程列表的同时,调度的时候,还需要进行切换并且记录每个进程的执行节点,也就是记录上下文(各种变量等等乱七八糟的东西,虽然你看不到,但是操作系统都要做),这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。就看我们上面的一些代码例子,你会发现有些程序是不是执行的时候比较慢才出结果,就是这个原因,那么我们要怎么做呢?

  在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果

 

 

# import time
# from multiprocessing import Process,Pool
#
# def func(n):
#     time.sleep(1)
#     print(n)
# if __name__ == '__main__':
#     pool = Pool()
#     pool.map(func,range(100))


import time
from multiprocessing import Process,Pool

def func(n):
    for i in range(5):
        n = n+1
    time.sleep(1)
    print(n)
if __name__ == '__main__':
    pool_start_time = time.time()
    pool = Pool(4)
    pool.map(func,range(100))    #自带join功能,异步执行
    pool_end_time = time.time()
    pool_dif_time = pool_end_time - pool_start_time


    pp_s_time = time.time()
    list_p = []
    for i in range(100):
        p1 = Process(target = func,args=(i,))
        p1.start()
        list_p.append(p1)
    [p.join() for p in list_p]
    pp_e_time = time.time()
    pp_dif_time = pp_e_time - pp_s_time
    print('进程池的时间',pool_dif_time)
    print('多进程的时间', pp_dif_time)

进程池同步执行的方法

# from multiprocessing import Process,Pool
# import time
# def func(i):
#     time.sleep(0.5)
#     return i**2
# if __name__ == '__main__':
#     p =Pool(4)
#     for i in range(10):
#         res = p.apply(func,args=(i,))
#         print(res)

进程池异步执行的方法

# from multiprocessing import Process,Pool
# import time
# def func(i):
#     time.sleep(0.5)
#     return i**2
# if __name__ == '__main__':
#     p =Pool(4)
#     res_list = []
#     for i in range(10):
#         res = p.apply_async(func,args=(i,))
#         res_list.append(res)
#         # print(res)
#     for i in res_list:
#         print(i.get())

进程池map传参

import time
from multiprocessing import Process,Pool

def func(n):
    print(n)
if __name__ == '__main__':
    pool = Pool(4)
    # pool.map(func,range(100))
    pool.map(func,['sb','ff',(1,23)])

 

 进程池回调函数

需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数,这是进程池特有的,普通进程
没有这个机制,但是我们也可以通过进程通信来拿到返回值,进程池的这个回调也是进程通信的机制完成的。 我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果

 回调函数在写的时候注意一点,回调函数的形参执行有一个,如果你的执行函数有多个返回值,那么也可以被回调函数的这一个形参接收,接收的是一个元祖,包含着你执行函数的所有返回值。

  

  使用进程池来搞爬虫的时候,最耗时间的是请求地址的网络请求延迟,那么如果我们在将处理数据的操作加到每个子进程中,那么所有在进程池后面排队的进程就需要等更长的时间才能获取进程池里面的执行进程来执行自己,所以一般我们就将请求作成一个执行函数,通过进程池去异步执行,剩下的数据处理的内容放到另外一个进程或者主进程中去执行,将网络延迟的时间也利用起来,效率更高。

  requests这个模块的get方法请求页面,就和我们在浏览器上输入一个网址然后回车去请求别人的网站的效果是一样的。安装requests模块的指令:在cmd窗口执行pip install requests。

import time,os
from multiprocessing import Process,Pool

def func1(n):
    print('func1>>>',os.getpid())
    return n*n
def func2(nn):
    print('func2>>>',os.getpid())
    print(nn)
if __name__ == '__main__':
    print('主进程',os.getpid())
    p =Pool(4)
    p.apply_async(func1,args= (10,),callback = func2)
    p.close()
    p.join()

apply_async的其他方法

# import time
# from multiprocessing import Process,Pool
# def fun(i):
#     time.sleep(1)
#     print(i)
#     return i**2
# if __name__ == '__main__':
#     p = Pool(4)
#     res_list = []
#     for i in range(10):
#         res = p.apply_async(fun,args = (i,))#同步执行的方法,他会等待你的任务的返回结果
#         res_list.append(res)
#     p.close()  #不是关闭进程池,而是不允许再有其他任务来使用进程池
#     p.join()#这是感知进程池中任务的方法,进程池中所有的进程随着主进程的结束而结束了,
#     #等待进程池的任务全部执行完
#
#     #apply_async异步执行
#     time.sleep(2)
#     for e_res in res_list:
#         print('结果',e_res.get())
#     print('主进程结束')

# import time
# from multiprocessing import Pool,Process
#
# def fun(i):
#     time.sleep(1)
#     print(i)
#     return i**2
# if __name__ == '__main__':
#     p =Pool(4)
#     res_list = []
#     for i in range(10):
#         res = p.apply_async(fun,args =(i,))
#         res_list.append(res)
#     print(res_list)
#     p.close()
#     p.join()
# #循环打印结果
#     for i in res_list:
#         print('结果',i.get())
#     print('主进程结束')
#
#     #进程池包含同步和异步方法

线程开头

https://www.cnblogs.com/clschao/articles/9684694.html    线程

进程https://www.cnblogs.com/clschao/articles/9629392.html?tdsourcetag=s_pcqq_aiomsg

from threading import Thread
import time

# def fun(n):
#     time.sleep(3)
#     print(n)
#
# if __name__ == '__main__':
#     t = Thread(target=fun,args = (1,))
#     t.start()
#     t.join()
#     print('主线程')


# 第二种创建线程的方法
# class MyThread(Thread):
#     def __init__(self,n):
#         super().__init__()   #要继承原本的参数信息
#         self.n = n
#     def run(self):
#         print('啦啦啦啦')
#         print('self.n',self.n)
# if __name__ == '__main__':
#     t =MyThread('NIHAO')
#     t.start()
#     t.join()
#     print('主线程结束')

 

posted on 2018-11-07 23:10  小王子QAQ  阅读(75)  评论(0)    收藏  举报