7.进程间通信(IPC)方式二:管道

(1)创建管道的类:

Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道

(2)参数介绍:

dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。

 

(3)方法介绍:

主要方法:

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异常。

(4)基于管道实现进程间通信(与队列的方式是类似的,队列就是管道加锁实现的)

from multiprocessing import Process,Pipe
def consumer(p,name):
left,right=p
left.close()
while True:
try:
baozi=right.recv()
print('%s 收到包子:%s' %(name,baozi))
except EOFError:
right.close()
break def producer(seq,p):
left,right=p
right.close()
for i in seq:
left.send(i)
# time.sleep(1)
else:
left.close() if __name__ == '__main__':
left,right=Pipe() c1=Process(target=consumer,args=((left,right),'c1'))
c1.start() seq=(i for i in range(5))
producer(seq,(left,right)) right.close()
left.close() c1.join()
print('主进程')
运行结果:
c1 收到包子:0
c1 收到包子:1
c1 收到包子:2
c1 收到包子:3
c1 收到包子:4
主进程

注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。

(5)管道可以用于双向通信,利用通常在客户端/服务器中使用的请求/响应模型或远程过程调用,就可以使用管道编写与进程交互的程序

from multiprocessing import Process,Pipe
def adder(p,name):
server,client=p
client.close()
while True:
try:
x,y=server.recv()
except EOFError:
server.close()
break
res=x+y
server.send(res)
print('server done') if __name__ == '__main__':
server,client=Pipe() c1=Process(target=adder,args=((server,client),'c1'))
c1.start() server.close() client.send((10,20))
print(client.recv())
client.close() c1.join()
print('主进程')
运行结果:
30
server done
主进程

注意:send()和recv()方法使用pickle模块对对象进行序列化。

8.进程间通信方式三:共享数据

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

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

通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,

还可以扩展到分布式系统中

进程间通信应该尽量避免使用本节所讲的共享数据的方式

进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的

虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此。

from multiprocessing import Manager,Process
import os
def work(d,l):
l.append(os.getpid())
d[os.getpid()]=os.getpid() if __name__ == '__main__':
m=Manager()
l=m.list(['init',])
d=m.dict({'name':'egon'}) p_l=[]
for i in range(5):
p=Process(target=work,args=(d,l))
p_l.append(p)
p.start() for p in p_l:
p.join()
print(d)
print(l)
运行结果:
{6568: 6568, 5092: 5092, 11400: 11400, 11724: 11724, 12092: 12092, 'name': 'egon'}
['init', 12092, 5092, 11400, 11724, 6568]

9.进程同步(锁),信号量,事件

加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。

进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理

所以,就让我们帮文件当做数据库,模拟抢票(Lock互斥锁)

from multiprocessing import Process,Lock
import json
import time
import random
def work(dbfile,name,lock):
# lock.acquire()
with lock:
with open(dbfile,encoding='utf-8') as f:
dic=json.loads(f.read()) if dic['count'] > 0:
dic['count']-=1
time.sleep(random.randint(1,3)) #模拟网络延迟
with open(dbfile,'w',encoding='utf-8') as f:
f.write(json.dumps(dic))
print('\033[43m%s 抢票成功\033[0m' %name)
else:
print('\033[45m%s 抢票失败\033[0m' %name)
# lock.release() if __name__ == '__main__':
lock=Lock()
p_l=[]
for i in range(100):
p=Process(target=work,args=('a.txt','用户%s' %i,lock))
p_l.append(p)
p.start() for p in p_l:
p.join()
print('主进程')

互斥锁

同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去,如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。一旦释放,就有人可以获得一把锁

信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念

from multiprocessing import Process,Semaphore
import time,random def go_wc(sem,user):
sem.acquire()
print('%s 占到一个茅坑' %user)
time.sleep(random.randint(0,3)) #模拟每个人拉屎速度不一样,0代表有的人蹲下就起来了
sem.release() if __name__ == '__main__':
sem=Semaphore(5)
p_l=[]
for i in range(13):
p=Process(target=go_wc,args=(sem,'user%s' %i,))
p.start()
p_l.append(p) for i in p_l:
i.join()
print('============》')

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

clear:将“Flag”设置为False

set:将“Flag”设置为True

from multiprocessing import Process,Event
import time,random def car(e,n):
while True:
if not e.is_set(): #Flase
print('\033[31m红灯亮\033[0m,car%s等着' %n)
e.wait()
print('\033[32m车%s 看见绿灯亮了\033[0m' %n)
time.sleep(random.randint(3,6))
if not e.is_set():
continue
print('走你,car', n)
break def police_car(e,n):
while True:
if not e.is_set():
print('\033[31m红灯亮\033[0m,car%s等着' % n)
e.wait(1)
print('灯的是%s,警车走了,car %s' %(e.is_set(),n))
break def traffic_lights(e,inverval):
while True:
time.sleep(inverval)
if e.is_set():
e.clear() #e.is_set() ---->False
else:
e.set() if __name__ == '__main__':
e=Event()
# for i in range(10):
# p=Process(target=car,args=(e,i,))
# p.start() for i in range(5):
p = Process(target=police_car, args=(e, i,))
p.start()
t=Process(target=traffic_lights,args=(e,10))
t.start() print('============》')

9.进程池

开多进程的目的是为了并发,如果有多核,通常有几个核就开几个进程,进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行),但很明显需要并发执行的任务要远大于核数,这时我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数...

当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

而且对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。

(1)创建进程池的类

Pool([numprocess [,initializer [, initargs]]]):创建进程池

(2)参数介绍

numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值

initializer:是每个工作进程启动时要执行的可调用对象,默认为None

initargs:是要传给initializer的参数组

 

(3)方法介绍

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():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成5 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用

方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法

obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。

obj.ready():如果调用完成,返回True

obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常

obj.wait([timeout]):等待结果变为可用。

obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数

(4)应用

from multiprocessing import Pool
import time
def work(n):
print('开工啦...')
time.sleep(3)
return n**2 if __name__ == '__main__':
q=Pool() #异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果,否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
res=q.apply_async(work,args=(2,))
q.close()
q.join() #join在close之后调用
print(res.get()) #同步apply用法:主进程一直等apply提交的任务结束后才继续执行后续代码
# res=q.apply(work,args=(2,))
# print(res)
from multiprocessing import Process,Pool
from socket import *
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,addr):
print(os.getpid())
while True: #通讯循环
try:
msg=conn.recv(1024)
if not msg:break
conn.send(msg.upper())
except Exception:
break
if __name__ == '__main__':
pool=Pool()
res_l=[]
while True: #链接循环
conn,addr=server.accept()
# print(addr)
# pool.apply(talk,args=(conn,addr))
res=pool.apply_async(talk,args=(conn,addr))
res_l.append(res)
# print(res_l)

server端

#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,client_addr):
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()
while True:
conn,client_addr=server.accept()
p.apply_async(talk,args=(conn,client_addr))
# p.apply(talk,args=(conn,client_addr)) #同步的话,则同一时间只有一个客户端能访问

客户端

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'))

10.回调函数

当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。

from multiprocessing import Pool
import time,random def get_page(url):
time.sleep(random.randint(1,3))
print('下载页面: %s' %url)
return {'url':url} #模拟下载后的结果 def parse_page(page_content):
time.sleep(1)
print('解析页面: %s' %page_content) if __name__ == '__main__':
urls=[
'http://maoyan.com/board/7',
'http://maoyan.com/board/1',
'http://maoyan.com/board/2'
]
p=Pool()
res_l=[]
for url in urls:
res=p.apply_async(get_page,args=(url,),callback=parse_page)
res_l.append(res) for i in res_l:
i.get()

应用

from multiprocessing import Pool
import time,random
import requests
import re
import json def get_page(url,pattern):
response=requests.get(url)
if response.status_code == 200:
return (response.text,pattern) def parse_page(info):
page_content,pattern=info
res=re.findall(pattern,page_content) for item in res:
dic={
'index':item[0],
'title':item[1],
'actor':item[2].strip()[3:],
'time':item[3][5:],
'score':item[4]+item[5] }
with open('db.txt','a',encoding='utf-8') as f:
f.write('%s\n' %json.dumps(dic))
if __name__ == '__main__':
pattern1=re.compile(r'<dd>.*?board-index.*?>(\d+)<.*?title="(.*?)".*?star.*?>(.*?)<.*?releasetime.*?>(.*?)<.*?integer.*?>(.*?)<.*?fraction.*?>(.*?)<',re.S) url_dic={
'http://maoyan.com/board/7':pattern1,
}
p=Pool()
res_l=[]
for url,pattern in url_dic.items():
res=p.apply_async(get_page,args=(url,pattern),callback=parse_page)
res_l.append(res) for i in res_l:
i.get()

使用进程池(非阻塞,apply_async)

from multiprocessing import Process,Pool
import time def func(msg):
print( "msg:", msg)
time.sleep(1)
return 'hahaha' 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)
print("==============================>")
pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print("Sub-process(es) done.")
for i in res_l:
print(res.get())

使用进程池(阻塞,apply)

from multiprocessing import Process,Pool
import time def func(msg):
print( "msg:", msg)
time.sleep(0.1)
return 'hahaha' 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("Sub-process(es) done.")
print(res_l)
for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法
print(res)

多个进程池

import multiprocessing
import os, time, random def Lee():
print("\nRun task Lee-%s" %(os.getpid())) #os.getpid()获取当前的进程的ID
start = time.time()
time.sleep(random.random() * 10) #random.random()随机生成0-1之间的小数
end = time.time()
print('Task Lee, runs %0.2f seconds.' %(end - start)) def Marlon():
print("\nRun task Marlon-%s" %(os.getpid()))
start = time.time()
time.sleep(random.random() * 40)
end=time.time()
print('Task Marlon runs %0.2f seconds.' %(end - start)) def Allen():
print("\nRun task Allen-%s" %(os.getpid()))
start = time.time()
time.sleep(random.random() * 30)
end = time.time()
print('Task Allen runs %0.2f seconds.' %(end - start)) def Frank():
print("\nRun task Frank-%s" %(os.getpid()))
start = time.time()
time.sleep(random.random() * 20)
end = time.time()
print('Task Frank runs %0.2f seconds.' %(end - start)) def Egon():
print("\nRun task Egon-%s" %(os.getpid()))
start = time.time()
time.sleep(random.random() * 20)
end = time.time()
print('Task Egon runs %0.2f seconds.' %(end - start)) def Lily():
print("\nRun task Lily-%s" %(os.getpid()))
start = time.time()
time.sleep(random.random() * 20)
end = time.time()
print('Task Lily runs %0.2f seconds.' %(end - start)) if __name__=='__main__':
function_list= [Lee, Marlon, Allen, Frank, Egon, Lily]
print("parent process %s" %(os.getpid())) pool=multiprocessing.Pool(4)
for func in function_list:
pool.apply_async(func) #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中 print('Waiting for all subprocesses done...')
pool.close()
pool.join() #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
print('All subprocesses done.')

Python编程-多进程二的更多相关文章

  1. python并发编程&多进程(二)

    前导理论知识见:python并发编程&多进程(一) 一 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_cou ...

  2. python并发编程&多进程(一)

    本篇理论居多,实际操作见:  python并发编程&多进程(二) 一 什么是进程 进程:正在进行的一个过程或者说一个任务.而负责执行任务则是cpu. 举例(单核+多道,实现多个进程的并发执行) ...

  3. python 使用多进程实现并发编程/使用queue进行进程间数据交换

    import time import os import multiprocessing from multiprocessing import Queue, pool ""&qu ...

  4. Python并发编程-多进程

    Python并发编程-多进程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.多进程相关概念 由于Python的GIL全局解释器锁存在,多线程未必是CPU密集型程序的好的选择. ...

  5. Python编程核心内容之二——切片、迭代和列表生成式

    Python版本:3.6.2  操作系统:Windows  作者:SmallWZQ 最近太忙啦.很多事情需要自己处理,感觉时间不够用啊~~~~今后,博客更新时间可能会慢下来,哈哈,正所谓"人 ...

  6. Python 3 并发编程多进程之进程同步(锁)

    Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1. ...

  7. Python 3 并发编程多进程之守护进程

    Python 3 并发编程多进程之守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemo ...

  8. Python 3 并发编程多进程之队列(推荐使用)

    Python 3 并发编程多进程之队列(推荐使用) 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 可以往 ...

  9. python 并发编程 多进程 目录

    python multiprocessing模块 介绍 python 开启进程两种方法 python 并发编程 查看进程的id pid与父进程id ppid python 并发编程 多进程 Proce ...

随机推荐

  1. RL for Robots

    1.Robot gains Social Intelligence through Multimodal Deep Reinforcement Learning 这篇文章使用DQN去训练一个机器人,使 ...

  2. 接入qq登录功能出现的问题

    在调用qq授权的接口时,出现以上错误. 原因是: 打包的应用签名和第一次上传包的签名不一致造成的 解决方法: 第一种方法:用上次打包apk的keystore重新打包apk,使签名一致. 第二种方法:联 ...

  3. spark hive结合杂记(hive-site.xml)

    1.下载spark源码,在spark源码目录下面有个make-distribution.sh文件,修改里面的参数,使编译后能支持hive,修改后执行该文件.(要预先安装好maven才能编译). 2.将 ...

  4. 关闭QQ看点

    手机qq联系人 然后右上角公众号 然后看到看点 取消关注!!!

  5. ubuntu在vmware下使用问题

    问题1: 在vmware虚拟机下安装的ubuntu系统,为了在windows和ubuntu之间拷贝数据方便,所以在vmware上安装了vmware tools.在安装了vmware tool之后会在u ...

  6. openwrt源码下载移植

    openwrt源码下载移植 一.源码下载 1.1下载 由于本人多次尝试从SVN代码地址下载代码,出现无法访问因此这里选择从Git代码地址进行下载.在linux系统命令终端输入 git clone gi ...

  7. ios 制作自已的framework

    本文转载至 http://blog.csdn.net/chen505358119/article/details/9190731   ios中我们写的代码有时不愿意让别人看到,可能对它进行封装,生成一 ...

  8. 导出大量数据到excel表

    set_time_limit(0);error_reporting(0); // 输出Excel文件头,可把user.csv换成你要的文件名header('Content-Type: applicat ...

  9. SQL创建表脚本

    <1>SQL Server设置主键自增长列 SQL Server设置主键自增长列   1.新建一数据表,里面有字段id,将id设为为主键   www.2cto.com   create t ...

  10. 一张图玩转H5测试

    背景 随着各种H5页面的普及和运用,并深深的影响着我们各个业务的发展,前两年也对H5测试的有着不少积累,但都是根据项目的要求,这里测试下,那里测试下,今年上半年专门成立了H5测试研究虚拟小组,专门研究 ...