python 管道、数据共享、进程池

一、管道(Pipe)(了解) (详情参考:https://www.cnblogs.com/clschao/articles/9629392.html)

  进程间通信(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 <--->另一进程的conn2或者一个进程conn2 <--->另一进程的conn1进行通信。recv未结收到消息,会阻塞。发送端管道端口关闭,再发送消息,报错EOFError,接收端堵塞,则报错:OSError。

from multiprocessing import Pipe,Process

def func(conn1,):
    # conn2.close()
    msg = conn1.recv()
    print('>>>>>',msg)

if __name__ == '__main__':
    conn1,conn2 = Pipe()

    p = Process(target=func,args=(conn1,))
    p.start()
    # conn1.close()
    conn2.close()
    conn2.send('小鬼!')

    print('主进程结束')


#EOFError #发送端堵塞,无法发送
#OSError #接收端堵塞,无法接受
send、recv、close
关于管道会造成数据不安全问题的官方解释:
    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方法(除其他之外)。注意,如果两个进程(或线程)试图同时从管道的同一端读取或写入数据,那么管道中的数据可能会损坏。当然,在使用管道的不同端部的过程中不存在损坏风险。
管道官方解释

二、数据共享Manager(了解)

  进程之间数据共享的模块之一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使用的基本步骤;

    m=Manager()

    dic=m.dict({"name":"sbalex",}

    之后就可以贡各个进程使用,并修改

    (存在数据安全问题,慎用)

from multiprocessing import Process,Manager,Lock

def func1(dic,loc):
    with loc:
        dic["num"]-=1

if __name__ == '__main__':
    m=Manager()
    loc=Lock()
    dic=m.dict({"num":100,})
    p_lst=[]
    for i in range(10):
        p=Process(target=func1,args=(dic,loc))
        p_lst.append(p)
        p.start()

        # p.join()
    [pp.join() for pp in p_lst]
    print(dic)
Manage+Lock使用实例

三、进程池Pool(重点)

  

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

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

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

  总结:进程池中可以制定创建若干个进程,在程序执行的过程中,即使进程结束了也不会关闭进程,等待下一个任务进入进程池后,拿着空闲的进程继续干活.因此处理一大批任务的时候,只需要创建若干个进程,就可以完成所有的任务,节约了创建和销毁进程的时间,减轻的cpu和硬盘的负担.

  2、multiprocess.Pool 模块

   创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务(高级一些的进程池可以根据你的并发量,搞成动态增加或减少进程池中的进程数量的操作),不会开启其他进程,提高操作系统效率,减少空间的占用等。

  创建方法:

Pool([numprocess  [,initializer [, initargs]]]):创建进程池
p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
'''需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()'''

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

P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
主要方法介绍

  (1)pool.map(func,可迭代对象) 表示可迭代对象分别把所有的值依次传递给func执行,并创建进程,内部有join机制

from multiprocessing import Process,Pool
import  time
def func1(i):
    num=i
    for j in range(5):
        num+=j


if __name__ == '__main__':
    #不使用进程池
    start_time=time.time()
    p_lst=[]
    for i in range(1000):
        p=Process(target=func1,args=(i,))
        p_lst.append(p)
        p.start()

    [pp.join() for pp in p_lst]
    end_time=time.time()
    print("不适用进程池",end_time-start_time)

    #引入进程池
    s_time=time.time()
    pool=Pool(8)
    pool.map(func1,range(1000)) #自动join,pool.map(函数,可迭代对象)
    e_time=time.time()
    print("使用进程池>>>",e_time-s_time)
不使用进程池和使用进程池效率对比
执行结果:
不适用进程池 36.224305391311646
使用进程池>>> 0.30788564682006836
结果显示

  

  注意:有一点,map是异步执行的,并且自带close和join

    一般约定俗成的是进程池中的进程数量为CPU的数量,工作中要看具体情况来考量。

  (2)pool.apply(func,不可迭代对象) 表示给func传参并创建进程。

      pool.apply——async(func,不可迭代对象) 表示给func传参并创建进程。

        pool.get()  进程池执行任务返回的是对象,需要用get方法获取返回值的内容。

        pool.close() 不是关闭进程池,而是停止进程池接受新任务,这样才能感知进程池是否把所有任                                                   务执行完毕

        pool.join() 主程序等待进程池执行完所有任务再结束,必须在pool.close之后。

from multiprocessing import Process,Pool
import time
def func(i):
    num=0
    time.sleep(1)
    for j in range(5):
        num+=i
    return num
if __name__ == '__main__':
    pool=Pool(4) #进程池中创建4个进程,以后一直都是这四个进程在执行任务
    for i in range(10):
        #apply 串行执行
        #同步调用,直到本次任务执行完毕拿到ret,等待任务work执行的过程中可能有阻塞也可能没有阻塞
        ret=pool.apply(func,args=(i,))#不能传入可迭代对象
        print(ret)
apply 同步(串口)执行任务
import time
from multiprocessing import Process,Pool

def func(i):
    num=0
    time.sleep(1)
    for j in range(5):
        num+=i
    # print(num)
    return num
if __name__ == '__main__':
    pool=Pool(4)
    lst=[]
    for  i in range(10):
        ret=pool.apply_async(func,args=(i,))
        # print(ret.get()) #放在for 循环中,创建一个进程取一个值
        lst.append(ret)
    
    pool.close() #不是关闭进程池,而是结束进程池接收任务,确保没有新任务再提交过来。
    # 感知进程池中的任务已经执行结束,只有当没有新的任务添加进来的时候,才能感知到任务结束了,所以在join之前必须加上close方法
    pool.join()
    # 使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
    [print(el.get()) for el in lst]
    
apply_async 异步执行进程

  注意:异步apply_async用法:如果使用异步提交的任务,主程序需要用join,等待进程池中的任务处理完毕后才可以用get获取结果。使用join之前,必须使用pool.close,结束进程接受任务。否则无法感知是否会有进程进入进程池,就不会知道何时进程池没有任务。

#一:使用进程池(异步调用,apply_async)
#coding: utf-8
from multiprocessing import Process,Pool
import time

def func(msg):
    print( "msg:", msg)
    time.sleep(1)
    return msg

if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res=pool.apply_async(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res)
        # s = res.get() #如果直接用res这个结果对象调用get方法获取结果的话,这个程序就变成了同步,因为get方法直接就在这里等着你创建的进程的结果,第一个进程创建了,并且去执行了,那么get就会等着第一个进程的结果,没有结果就一直等着,那么主进程的for循环是无法继续的,所以你会发现变成了同步的效果
    print("==============================>") #没有后面的join,或get,则程序整体结束,进程池中的任务还没来得及全部执行完也都跟着主进程一起结束了

    pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

    print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果
    for i in res_l:
        print(i.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get

#二:使用进程池(同步调用,apply)
#coding: utf-8
from multiprocessing import Process,Pool
import time

def func(msg):
    print( "msg:", msg)
    time.sleep(0.1)
    return msg

if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res=pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个
    print("==============================>")
    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

    print(res_l) #看到的就是最终的结果组成的列表
    for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法
        print(i)
详解apply和apply_async

  #进程池版socket聊天代码:

#Pool内的进程数默认是cpu核数,假设为4(查看方法os.cpu_count())
#开启6个客户端,会发现2个客户端处于等待状态
#在每个进程内查看pid,会发现pid使用为4个,即多个客户端公用4个进程
from socket import *
from multiprocessing import Pool
import os

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn):
    print('进程pid: %s' %os.getpid())
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__':
    p=Pool(4)
    while True:
        conn,*_=server.accept()
        p.apply_async(talk,args=(conn,))
        # p.apply(talk,args=(conn,client_addr)) #同步的话,则同一时间只有一个客户端能访问

server端:tcp_server.py
服务器
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))
客户端

  

  (3)回调函数

需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数,这是进程池特有的,普通进程没有这个机制,但是我们也可以通过进程通信来拿到返回值,进程池的这个回调也是进程通信的机制完成的。

我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果
回调函数简介

  注意:回调函数是在主进程执行的,但是如果主进程未接受到pool.apply_async函数内部的的回调信息,则可能会提前关闭子进程.

import time
from multiprocessing import Pool,Process

import os

def func(n):
    # print('xxxxxxxxxx')
    print('子进程的pid :',os.getpid())
    return n*n,'约吗'

def call_back_func(x):
    # print(x)  #(9, '约吗')
    print('call_back pid ::',os.getpid())
    print(x[0])

if __name__ == '__main__':
    pool = Pool(4)
    pool.apply_async(func,args=(3,),callback=call_back_func)

    print('主进程的pid:',os.getpid())
    pool.close()
    pool.join()
验证回调函数在主进程执行

  代码显示:回调函数与主进程函数的pid号一致,说明了回调函数在主进程中执行.

  

猜你喜欢

转载自www.cnblogs.com/angle6-liu/p/10038709.html