Python学习 Day 035 -管道&进程池&Manager&线程初识

主要内容:

  • 1.管道
  • 2. 数据共享-manager模块
  • 3进程池

1.管道

(1)管道是进程间通信的第二种方式,但是不推荐使用,因为管道会导致数据不安全的情况出现

#创建管道的类:
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异常。
管道概述
from multiprocessing import Process,Pipe
# def func(conn2):
#     msg = conn2.recv()
#     print(">>>>",msg)
#     msg2 = conn2.recv()    # 会报错  EOFError  ---如果管道另一端关闭了,那么再接收消息的时候就会报这个错

def func (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=(conn2,))          #将管道的一端给子进程
    p.start()                   #开启子进程
    conn1.send("小鬼")          #主进程发送消息
    conn1.close()               #管道的一端关闭
管道使用示例-错误1
from multiprocessing import Process,Pipe
# def func(conn2):
#     msg = conn2.recv()
#     print(">>>>",msg)
#     msg2 = conn2.recv()    # 会报错  EOFError  ---如果管道另一端关闭了,那么再接收消息的时候就会报这个错

def func (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=(conn2,))          #将管道的一端给子进程
    p.start()                   #开启子进程
    conn1.send("小鬼")          #主进程发送消息
    conn1.close()
    conn1.recv()                #OSError: handle is closed
管道使用示例-错误2

#注意:使用管道的时候,如果要关闭管道,要么全部关闭,要么在recv时候使用Try-- except

(2)管道通信不安全

关于管道会造成数据不安全问题的官方解释:
    The two connection objects returned by Pipe() represent the two ends of the pipe. Each connection object has send() and recv() methods (among others). Note that data in a pipe may become corrupted if two processes (or threads) try to read from or write to the same end of the pipe at the same time. Of course there is no risk of corruption from processes using different ends of the pipe at the same time.
    
    由Pipe方法返回的两个连接对象表示管道的两端。每个连接对象都有send和recv方法(除其他之外)。注意,如果两个进程(或线程)试图同时从管道的同一端读取或写入数据,那么管道中的数据可能会损坏。当然,在使用管道的不同端部的过程中不存在损坏风险。
管道造成数据不安全的解释

2. 数据共享-manager模块

进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.

Manager模块介绍
Manager模块介绍
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)
Manager-示例

多进程共同去处理共享数据时,不加锁就会出现错误的结果,进程不安全,所以也需要加锁

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

if __name__ == '__main__':
    m = Manager()
    ml = Lock()                   # 创建一个锁
    m_dic = m.dict({"count":100})
    #开启20个进程来修改数据
    p_lst = []
    for i in range(20):
        p = Process(target=func,args=(m_dic,ml,))
        p.start()
        p_lst.append(p)
    [p.join() for p in p_lst]
    print("主进程",m_dic)   #主进程 {'count': 80} or 主进程 {'count': 81} 
                             # 不加锁时会出现就是当其中一个进程将数据读取还未修改时另一个进程也读取了数据,这时他们修改的结果是一样的
不加锁示例
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})
    # print('主进程', m_dic)
    p_list = []
    #开启20个进程来对共享数据进行修改
    for i in range(20):
        p1 = Process(target=func,args=(m_dic,ml,))
        p1.start()
        p_list.append(p1)
    [ppp.join() for ppp in p_list]

    print('主进程',m_dic)         #主进程 {'count': 80} 加锁后每次进程执行修改操作后只会是一个
加锁示例,数据不会错乱

小结:

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

工作中常用的是锁,信号量和事件不常用,但是信号量和事件面试的时候会问到,你能知道就行
进程之间的通信:队列、管道、数据共享

3.进程池

为什么要有进程池?进程池的概念。

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

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

(1)进程池方法介绍

# p.apply(func,[, args [, kwargs]])  #在一个进程池中执行func(*args,**kwargs)然后返回结果
#p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
'''此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,
将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。'''
#p.close():关闭进程池,防止进一步操作,如果所有操作持续挂起,他们将在终止进程前完成
# P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
进程池方法介绍

(2)进程池与创建进程时间对比

import time
from multiprocessing import Pool,Process
#计算下开多进程和进程池的执行效率
def func(n):
    for i in range(5):
        n = n + i
if __name__ == '__main__':
    #进程池模式
    pool_s_time = time.time()
    pool = Pool(4)    #创建含有4个进程的进程池   ****一般约定俗成的是进程池中的进程数量为CPU的数量,工作中要看具体情况来考量
    pool.map(func,range(200))    #异步调用进程,开启200个任务,map自带join和close功能
                                 #注意,map目前只限于接收一个可迭代的数据类型参数,
    #多进程模式
    pool_end_time = time.time()
    pool_dif_time = pool_end_time - pool_s_time

    p_s_time = time.time()
    p_lst= []
    for i in range(200):
        p = Process(target= func,args = (i,))
        p.start()
        p_lst.append(p)
    [p.join() for p in p_lst]
    p_e_time = time.time()
    p_dif_time = p_e_time - p_s_time

    print("进程池的执行时间",pool_dif_time)   #0.176239013671875      进程池 的效率高
    print("创建进程的执行时间",p_dif_time)    #7.825609922409058
进程池与创建进程的对比

(3)进程池的同步方法

import time
from multiprocessing import Pool

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)
进程池的同步执行方法

(4)进程池的异步执行

import time
from multiprocessing import Pool

def func(i):
    time.sleep(1)
    print(i)                              
    return i**2

if __name__ == '__main__':
    p = Pool(4)
    res_lst = []
    for i in range(10):
        res = p.apply_async(func,args=(i,))
        res_lst.append(res)

    p.close()   #不是关闭进程池,而是不允许其他任务再来使用进程池
    p.join()  #这是感知进程池中任务的方法,不加这个进程池中所有的任务会随着主进程的结束而结束,
              # 它的作用是等到进程池的任务全部执行完,然后结束主进程
    # time.sleep(2)

    for e_res in res_lst:
        print("结果",e_res.get())
  #进程池中还是4个4个出来,但是主进程中的结果却是一块出来
    print("主进程结束")  
进程池的异步执行

(5)进程池中的回调函数

import time
from multiprocessing import Pool

def func(i):
    time.sleep(1)
    print(i)                              
    return i**2

if __name__ == '__main__':
    p = Pool(4)
    res_lst = []
    for i in range(10):
        res = p.apply_async(func,args=(i,))
        res_lst.append(res)

    p.close()   #不是关闭进程池,而是不允许其他任务再来使用进程池
    p.join()  #这是感知进程池中任务的方法,不加这个进程池中所有的任务会随着主进程的结束而结束,
              # 它的作用是等到进程池的任务全部执行完,然后结束主进程
    # time.sleep(2)

    for e_res in res_lst:
        print("结果",e_res.get())
  #进程池中还是4个4个出来,但是主进程中的结果却是一块出来
    print("主进程结束")  
回调函数概述
import os
from multiprocessing import Pool

def func1(n):
    print('func1>>',os.getpid())
    # print('func1')
    return n*n

def func2(nn):
    print('func2>>',os.getpid())
    # print('func2')
    print(nn)
    # import time
    # time.sleep(0.5)
if __name__ == '__main__':
    print('主进程:',os.getpid())
    p = Pool(4)
    p.apply_async(func1,args=(10,),callback=func2)
    p.close()
    p.join()
回调函数的简单使用

猜你喜欢

转载自www.cnblogs.com/wcx666/p/9851141.html