python并发编程(二)——进程间的通信

进程间的控制:锁Lock,信号量Semaphore,事件Event
进程间的数据通信:队列(进程安全的)和管道(进程不安全的)

进程间的通信

用文件共享数据可以实现进程间通信,但也有如下的缺点:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
进程异步是为了提高效率,但是为了数据安全加锁后,将之改为同步
又降低的效率	
因此我们需要一种解决方案能够兼顾:
1、效率高(多个进程共享一块内存的数据)
2、数据安全问题的处理。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的
所以应该尽量避免使用共享数据,尽可能使用消息传递和队列,
避免处理复杂的同步和锁问题

队列

from multiprocessing import Queue 
可以进行进程间的通信,是线程安全的
队列是先进先出的数据类型
创建:
Queue([maxsize])
maxsize是队列中允许的最大长度。如果省略此参数,则无大小限制
put(index)添加数据,put的index值大于队列长度会发生阻塞现象
get()获取数据,当队列已经为空,会发生阻塞现象
full()查看队列是否已经满了,返回 (空)True/(非空)False, 多进程下不准确
empty()判断队列是否空,多进程下不准确
get_nowait()获取值,如果队列没有值不会阻塞而是抛出queue empty异常
qsize()返回队列中目前队列中的元素数量,多进程下不准确
from multiprocessing import Process,Queue #这个可以进行进程间的通信
#进程间的通信
def put(q):
    q.put('哈哈哈')
    
def get(q):
    if not q.empty():
        print(q.get())
    
if __name__ == '__main__':
    q = Queue()#创建队列
    p = Process(target = put,args=(q,))#创建进程,传入参数
    p.start()#启动进程
    p2 = Process(target = get,args=(q,))#创建进程,传入参数
    p2.start()#启动进程
    #两个进程间的队列必须是同一个队列对象

管道

from multiprocessing import Pipe
可以进行进程间的通信,是非线程安全的
from multiprocessing import Pipe
#管道
conn1,conn2 = Pipe()#创建一个管道并返回管道两端的连接
#print(conn2.recv()) #如果管道没有数据而直接去接收会发生阻塞

conn1.send('123') #在端口1发送字符串
print(conn2.recv())#在端口2可以接收字符串

conn2.send('456')#在端口2发送字符串
print(conn1.recv())#可以反向发送数据

管道的循环取值

from multiprocessing import Pipe,Process
#结束循环中的管道
#import time
def fun(c1,c2): 
    c2.close()
    while True:
        try:
            s = c1.recv()#向管道中获取数据
            print(s)
        except EOFError:#除了当前接收数据的连接c1外,管道其它的所有连接都关闭且管道无数据时,会触发EOFError
            c1.close()
            break

if __name__ == '__main__':
    c1,c2 = Pipe()
    
    Process(target = fun,args = (c1,c2)).start()#子进程与主进程的连接是不会相互影响的
    c1.close()
    for i in range(20):
        c2.send(i)#向管道中存放数据
    c2.close()

其他通信方式

from multiprocessing import Manager进程间通信模块
需要加锁控制数据的安全
from multiprocessing import Process,Manager,Lock
import time
def fun(dic,l):
    l.acquire()#加锁
    time.sleep(0.04)
    dic['count'] -= 1
    l.release()#释放锁
    #print('子进程',dic['count'])

if __name__ == '__main__':
    m = Manager() #进程间通信模块
    d = m.dict({'count':100}) #创建具有共享属性的字典,但是不安全
    locks = Lock() #需要加锁来控制进程间的通信
    l = []
    for i in range(30):
        p = Process(target = fun,args = (d,locks))
        p.start()
        l.append(p)
    
    for j in l :
        j.join()
    print('主进程',d['count'])
        

生产者/消费者模式

生产者生产数据过快,消费者处理数据太慢?
	可以设置一个容器(队列)存放生产数据,用容器的大小限制生产速度,容器满了就不在生产
生产者生产数据过慢,消费者处理数据过快?
	多增加几个生产源
如何判断生产者生成数据完毕?
	a.判断队列是否为空,判断生成代码是否执行完毕
			但是q.empty不适用于多进程护环境
	b.队列现在为空,可能生产者还未来得及生产,不一定生产者代码执行完毕
		所以不能以队列是否为空判断生产者执行完毕
		在生产者结束后再次向队列添加一个标记数据,消费者获取到标记数据就关闭进程
		但是在多进程的多个消费模式下,获取到标记数据的进程会关闭,未获取到标记数据的会继续阻塞
		所以有多少个消费者就添加多少个标记数据
import time
import random
from multiprocessing import Queue,Process
def consumer(name,q):
    while True:
        s = q.get()
        if( s== 1):
            break
        time.sleep(random.randint(1,3))
        print('%s消费了%s'%(name,s))
                 
def producer(name,food,q):
    for i in range(10):
        s = '%s产生第%d个%s'%(name,i,food)
        print(s)
        q.put(s)
        time.sleep(random.randint(1,3))
                   
if __name__ == '__main__':
    q = Queue(20)#新建队列,容量为20——数据缓冲区
    p1 = Process(target = producer,args = ('主机1','数据1',q))
    p1.start()#启动生产者p1进程
    p2 = Process(target = producer,args = ('主机2','数据2',q))
    p2.start()#启动生产者p2进程
    c1 = Process(target = consumer,args = ('主机3',q))
    c1.start()#启动消费者c1进程
    c2 = Process(target = consumer,args = ('主机4',q))
    c2.start()#启动消费者c2进程
    p1.join()#感知生成者p1进程的结束
    p2.join()#感知生成者p2进程的结束
    q.put(1)#标记数据
    q.put(1)#标记数据

改进

from multiprocessing import JoinableQueue
JoinableQueue模块比Queue模块多了两个方法,
在q.get数据后要提交一个回执q.task_done(),标记值-1,操作成功
队列每添加q.put()一个数据就自动增加一个标记值
q.join() 生产者代码执行结束后,阻塞当前代码直到消费者将数据全部处理完毕
import time
import random
from multiprocessing import JoinableQueue,Process
def consumer(name,q):
    while True:
        s = q.get()
        time.sleep(random.randint(1,3))
        print('%s消费了%s'%(name,s))
        q.task_done()    
          
def producer(name,food,q):
    for i in range(10):
        s = '%s生产了第%d个%s'%(name,i,food)
        print(s)
        q.put(s)
        time.sleep(random.randint(1,3))
        
    q.join() #阻塞,直到队列中所有数据全被处理完毕(消费者消费完毕)
    
if __name__ == '__main__':
    q = JoinableQueue(20)
    p1 = Process(target = producer,args = ('食堂阿姨','包子',q))
    p1.start()
    p2 = Process(target = producer,args = ('食堂大妈','馒头',q))
    p2.start()
    c1 = Process(target = consumer,args = ('学生1',q))
    c2 = Process(target = consumer,args = ('学生2',q))
    c1.daemon = True #将消费者设为守护进程——等待主进程代码执行完毕
    c2.daemon = True #将消费者设为守护进程——等待主进程代码执行完毕
    c1.start()  #启动消费者进程
    c2.start() #启动消费者进程
    p1.join() #感知生产者进程的结束——阻塞当前代码直到生产者生成完毕
    p2.join() #感知生产者进程的结束
#执行流程
#所有生产者代码执行结束后,队列.join()阻塞生产者,等待消费者处理完 
#消费者处理完数据,阻塞消失,生产者结束
#生产者结束,生产者进程.join()结束,主进程的代码执行完毕,守护线程(消费者结束)结束,然后主进程真正的结束

进程池

from multiprocessing import Pool
进程的创建和释放是最消耗系统资源的
过多的进程也会影响系统的调度
python中的进程池 先创建一个指定进程数量的容器称为进程池
其它应用需要进程时直接进程池中获取
进程池中进程数不够时等待其它进程将进程释放回进程池
高级进程池可以分析用户使用程序的低谷期和高峰期,设置进程池的最小进程数和最大进程数

示例一

from multiprocessing import Pool
import os
import time
def fun(dic):
    for i in range(10):
        dic += i
        print(dic)

if __name__ == '__main__':
    t1 = time.time()
    pool = Pool(5) #在进程池中开启5个进程,不写默认开启CPU个数的进程   
    pool.map(fun,range(20))#在进程池调用fun方法,开启20个任务(传入一个可迭代参数)
    #map方法,参数1函数名,参数2可迭代参数作为函数的实参
    #默认异步的执行任务,自带close(不在接收任务)与join(感知所有子进程的结束)
    #等价于 for i in range(20):
    #    p = Process(target = fun ,args =(i,))
    print(time.time() - t1)#进程池提高了程序的效率

示例二

from multiprocessing import Pool
import time
def fun(dic):
    for i in range(10):
        dic += i
        print(dic)
        
def fun2(dic):
    print(dic[0])
    print(dic[1])

if __name__ == '__main__':
    t1 = time.time()
    pool = Pool(5) #在进程池中开启5个进程
    pool.map(fun,range(20))
    pool.map(fun2,[('key',1),('value',2)])#利用map启动2个任务
    #map的第二个只能传递可迭代参数,每次传入可迭代参数的一个元素,如果要一次性传入多个参数,可以将元组作为列表元素来传递
    print(time.time() - t1)

示例三:进程池的同步调用

from multiprocessing import Pool
import time
import  os
def fun(s):
    print('start %s'%s,os.getpid())
    time.sleep(0.01)
    print('end %s'%s,os.getpid())
    #一个进程的start接着end,表示这是一个同步程序
       
if __name__ == '__main__':
    pool = Pool(5) #在进程池中开启5个进程
    for i in range(20):
        pool.apply(fun,args = (i,))#同步提交,可以有返回值      
        #需要与close与join同时使用
    pool.close()#进程池不在接收任务
    pool.join()#感知进程池中所有进程的任务执行结束
#start 0 7496
#end 0 7496
#start 1 7212
#end 1 7212    
    

示例四:进程池的异步调用

from multiprocessing import Pool
import time
import  os
def fun(s):
    print('start %s'%s,os.getpid())
    time.sleep(0.01)
    print('end %s'%s,os.getpid()

if __name__ == '__main__':
    pool = Pool(5) #在进程池中开启5个进程
    for i in range(20):       
        pool.apply_async(fun,args = (i,))#真异步,不会等待子进程结束,需要手动的close和join
        #需要与close与join同时使用
    pool.close()#进程池不在接收任务
    pool.join()#感知进程池中所有进程的任务执行结束
    
# start 0 7500
#start 1 4016
#start 2 3748
#end 0 7500   

进程池的返回值

p = Pool()创建进程池
p.map(functionName,iterable) 默认异步的执行任务,自带close与join
p.apply() 默认同步执行任务,可以有返回值
p.apply_async() 默认异步执行任务,和主进程完全异步,
需要手动的close和join,返回值需要get()获取,会阻塞当前进程,等待子进程运行完毕
from multiprocessing import Pool
import time
def fun(i):
    time.sleep(0.5)
    return i * i

if __name__ == '__main__':
    p = Pool(5)
    ret = p.map(fun,range(10)) #一次性返回所有返回值,如果进程任务过多时,apply_async()更好
    print(ret)#获取返回值
    #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    ret = p.apply(fun,args=(i,))
    print(ret)#获取返回值
   

异步返回值的获取

from multiprocessing import Pool
import time
def fun(i):
    time.sleep(0.5)
    return i * i

if __name__ == '__main__':
    p = Pool(5)
	l = []
     for i in range(10):
         ret = p.apply_async(fun,args=(i,))
         #print(ret.get()) #阻塞当前进程,等待子进程运行完毕
         #阻塞当前进程等待子进程1运行完毕获得ret1,用过ret1获取get值
         #阻塞当前进程等待子进程2运行完毕获得ret2,用过ret2获取get值         
         #...
         ret = p.apply_async(fun,args=(i,)) #改进
         l.append(ret)
    p.close()
    p.join()
    for i in l:
         print(i.get())

回调函数

回调函数 ,常用于爬虫爬取数据后进行数据处理
爬取数据最耗时的过程(主要是网络延迟和数据下载)可以使用多进程来处理
爬取数据后就可以用回调函数处理数据,释放的多进程资源继续去爬取数据
回调函数是在主进程中调用的
import os
from multiprocessing import Pool
def func1(n):
    print('func1进程',os.getpid())
    return n*n

def func2(nn):
    print('func2进程',os.getpid())
    print('func2数据',nn)

if __name__ == '__main__':
    print('主进程 :',os.getpid())
    p = Pool(5)
    for i in range(10):
        p.apply_async(func1,args=(10,),callback=func2)
        #先执行func1函数,将这个函数的返回值作为fun2函数的参数,fun1在子进程中执行,fun2在主进程中执行
    p.close()
    p.join()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值