python 异常处理、进程
目录:
- 异常处理
- python进程
- python并发之多进程
一、异常处理(try...except...)
1、程序中难免出现错误,而错误分成两种:
a.语法错误:
b.逻辑错误(逻辑错误)
2、异常定义:异常就是程序运行时发生错误的信号。
在python中,错误触发的异常,是以异常追踪信息、 异常类型、异常值三部分组成,如下:
执行的结果为:
#异常追踪信息
Traceback:
Traceback (most recent call last):
File "D:/python/day29/aa.py", line 5, in <module>
nulige
# 异常类型:异常值
NameError: name 'nulige' is not defined
3、异常的种类:
a.常用分类
b.更多分类
4、异常处理定义:
python解释器检测到错误,触发异常(也允许程序员自己触发异常)
程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理
5、异常处理的应用场景:
python解析器去执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就在当前异常处终止,后面的代码不会运行,谁会去用一个运行着突然就崩溃的软件。所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性 。
6、异常处理的使用
首先须知,异常是由程序的错误引起的,语法上的错误跟异常处理无关,必须在程序运行前就修正
a.使用if判断方式:
if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。
在你的程序中频繁的写与程序本身无关,与异常处理有关的if,可读性极其的差
这是可以解决异常的,只是存在上述两个的问题,所以,千万不要妄下定论if不能用来异常处理。
b.python为每一种异常定制了一个类型,然后提供了一种特定的语法结构用来进行异常处理
1)基本语法:
try: #异常捕捉
pass # 被检测的代码块
except <异常类型> as <e>:
pass # 一旦try检测到异常,就执行这个位置的逻辑
<except ...: > # 异常处理的多分枝
<else:>
pass # 报错执行逻辑
finally:
pass # 最后不管报不报错都执行的逻辑(为状态逻辑)
2)异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
3)多分支
4)万能异常:python万能异常,Exception,他可以捕获任意异常
5)主动触发异常
6)自定义异常
7)断言
7、try...except的方式与if的比较:
try..except这种异常处理机制就是取代if那种方式,让你的程序在不牺牲可读性的前提下增强健壮性和容错性。异常处理中为每一个异常定制了异常类型(python中统一了类与类型,类型即类),对于同一种异常,一个except就可以捕捉到,可以同时处理多段代码的异常(无需‘写多个if判断式’)减少了代码,增强了可读性。
使用try..except的方式
a.把错误处理和真正的工作分开来
b.代码更易组织,更清晰,复杂的工作任务更容易实现;
c.毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
8、使用异常处理注意事项。
try...except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的这种东西加的多了,会导致你的代码可读性变差。 异常处理只有在有些异常无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正。
9、总结:
1)try..except的方式,只是python提供给你一种特定的语法结构去做这件事,对于不同代码的同一种异常,python为你定制了一中类型,一个expect就可以捕捉到。
2)try...except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的这种东西加的多了,会导致你的代码可读性变差。异常处理只有在有些异常无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正。
二、python进程(multiprocessing)
1、操作系统哦你的概念:
操作系统位于底层硬件与应用软件之间的一层。工作方式:向下管理硬件,向上提供接口。
操作系统进行进程切换:1.出现IO操作;2.固定时间。
固定时间很短,人感受不到。每一个应用层运行起来的程序都是进程。
2、进程的概念:
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。需要强调的是:同一个程序执行两次,那也是两个进程。
进程:资源管理单位(容器)。
线程:最小执行单位,管理线程的是进程。
进程定义:
进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序
用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外
部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
3、进程与线程的关系:
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,控制该进程的地址空间。
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
4、并行和并发:
无论是并行还是并发,在用户看来都是'同时'运行的,而一个cpu同一时刻只能执行一个任务。
并行:同时运行,只有具备多个cpu才能实现并行。
并发:是伪并行,即看起来是同时运行,单个cpu+多道技术。
ps.多道技术:
内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,
这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,
即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)。
5、同步与异步
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
举个例子,打电话时就是同步通信,发短息时就是异步通信。
6、进程的创建
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
关于创建的子进程,UNIX和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间,任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
7、进程的终止
1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,程序员主动抛出异常,例如raise)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等)
4. 被其他进程杀死(非自愿,如kill -9)
8、进程的层次结构
无论UNIX还是windows,进程只有一个父进程,不同的是:
1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
9、进程的状态
tail -f access.log |grep '404'
执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道'|'通讯,将tail的结果作为grep的输入。
进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行
其实在两种情况下会导致一个进程在逻辑上不能运行,
1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作
2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU
因而一个进程由三种状态
10、进程并发的实现
进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)
该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样
三、python并发之多进程(multiprocessing模块)
1、multiprocessing模块介绍:
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
2、Process类的介绍:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
需要使用关键字的方式来指定参数,args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
1)group参数未使用,值始终为None
2)target表示调用对象,即子进程要执行的任务
3)args表示调用对象的位置参数元组,args=(1,2,'egon',)
4)kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5)name为子进程的名称
方法介绍:
1)p.start():启动进程,并调用该子进程中的p.run()
2)p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3)p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4)p.is_alive():如果p仍然运行,返回True
5)p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
1)p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2)p.name:进程的名称
3)p.pid:进程的pid
4)p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5)p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
3、Process类的使用
1.创建并开启子进程的两种方式
ps.注意:在windows中Process()必须放到# if __name__ == '__main__':下
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
a.方法一:函数方法调用
1 #开进程的方法一: 2 import time 3 import random 4 from multiprocessing import Process 5 def piao(name): 6 print('%s piaoing' %name) 7 time.sleep(random.randrange(1,5)) 8 print('%s piao end' %name) 9 10 11 12 p1=Process(target=piao,args=('egon',)) #必须加,号 13 p2=Process(target=piao,args=('alex',)) 14 p3=Process(target=piao,args=('wupeqi',)) 15 p4=Process(target=piao,args=('yuanhao',)) 16 17 p1.start() 18 p2.start() 19 p3.start() 20 p4.start() 21 print('主线程')
b. 方法二:类定义式调用
1 #开进程的方法二: 2 import time 3 import random 4 from multiprocessing import Process 5 6 7 class Piao(Process): 8 def __init__(self,name): 9 super().__init__() 10 self.name=name 11 def run(self): 12 print('%s piaoing' %self.name) 13 14 time.sleep(random.randrange(1,5)) 15 print('%s piao end' %self.name) 16 17 p1=Piao('p1') 18 p2=Piao('p2') 19 p3=Piao('p3') 20 p4=Piao('p4') 21 22 p1.start() #start会自动调用run 23 p2.start() 24 p3.start() 25 p4.start() 26 print('主线程')
c. socket结合process使用
1 from socket import * 2 from multiprocessing import Process 3 4 server=socket(AF_INET,SOCK_STREAM) 5 server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 6 server.bind(('127.0.0.1',8080)) 7 server.listen(5) 8 9 def talk(conn,client_addr): 10 while True: 11 try: 12 msg=conn.recv(1024) 13 if not msg:break 14 conn.send(msg.upper()) 15 except Exception: 16 break 17 18 if __name__ == '__main__': #windows下start进程一定要写到这下面 19 while True: 20 conn,client_addr=server.accept() 21 p=Process(target=talk,args=(conn,client_addr)) 22 p.start()
1 from socket import * 2 3 client=socket(AF_INET,SOCK_STREAM) 4 client.connect(('127.0.0.1',8080)) 5 6 7 while True: 8 msg=input('>>: ').strip() 9 if not msg:continue 10 11 client.send(msg.encode('utf-8')) 12 msg=client.recv(1024) 13 print(msg.decode('utf-8'))
ps.每来一个客户端,都在服务端开启一个进程,如果并发来一个万个客户端,要开启一万个进程吗,你自己尝试着在你自己的机器上开启一万个,10万个进程试一试。
解决方法:进程池
2.Process对象的其他方法或属性
a.进程对象的其他方法一:terminate,is_alive
terminate()# 关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
is_alive() # 检查子进程是否存活True/False
1 #进程对象的其他方法一:terminate,is_alive 2 from multiprocessing import Process 3 import time 4 import random 5 6 class Piao(Process): 7 def __init__(self,name): 8 self.name=name 9 super().__init__() 10 11 def run(self): 12 print('%s is piaoing' %self.name) 13 time.sleep(random.randrange(1,5)) 14 print('%s is piao end' %self.name) 15 16 17 p1=Piao('egon1') 18 p1.start() 19 20 p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活 21 print(p1.is_alive()) #结果为True 22 23 print('开始') 24 print(p1.is_alive()) #结果为False
b.进程对象的其他方法二:p.daemon=True,p.join
daemon() #默认为False,可以为True 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死
join()#等待子进程停止,在执行主进程,等0.0001秒就不再等了
1 #进程对象的其他方法二:p.daemon=True,p.join 2 from multiprocessing import Process 3 import time 4 import random 5 6 class Piao(Process): 7 def __init__(self,name): 8 self.name=name 9 super().__init__() 10 def run(self): 11 print('%s is piaoing' %self.name) 12 time.sleep(random.randrange(1,3)) 13 print('%s is piao end' %self.name) 14 15 16 p=Piao('egon') 17 p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死 18 p.start() 19 p.join(0.0001) #等待p停止,等0.0001秒就不再等了 20 print('开始')
1 from multiprocessing import Process 2 3 import time 4 import random 5 def piao(name): 6 print('%s is piaoing' %name) 7 time.sleep(random.randint(1,3)) 8 print('%s is piao end' %name) 9 10 p1=Process(target=piao,args=('egon',)) 11 p2=Process(target=piao,args=('alex',)) 12 p3=Process(target=piao,args=('yuanhao',)) 13 p4=Process(target=piao,args=('wupeiqi',)) 14 15 p1.start() 16 p2.start() 17 p3.start() 18 p4.start() 19 20 #注意:进程只要start就会在开始运行了,所以p1-p4.start()时,系统中已经有四个并发的进程了 21 #而我们p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键 22 #join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其余p2,p3,p4仍然在运行,等p1.join 23 #结束,可能p2,p3,p4早已经结束了,这样p2.join,p3.join.p4.join直接通过 24 # 所以4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间 25 p1.join() 26 p2.join() 27 p3.join() 28 p4.join() 29 30 print('主线程') 31 32 33 #上述启动进程与join进程可以简写为 34 # p_l=[p1,p2,p3,p4] 35 # 36 # for p in p_l: 37 # p.start() 38 # 39 # for p in p_l: 40 # p.join()
c.进程对象的其他属性:name,pid
1 #进程对象的其他属性:name,pid 2 from multiprocessing import Process 3 import time 4 import random 5 class Piao(Process): 6 def __init__(self,name): 7 # self.name=name 8 # super().__init__() #Process的__init__方法会执行self.name=Piao-1, 9 # #所以加到这里,会覆盖我们的self.name=name 10 11 #为我们开启的进程设置名字的做法 12 super().__init__() 13 self.name=name 14 15 def run(self): 16 print('%s is piaoing' %self.name) 17 time.sleep(random.randrange(1,3)) 18 print('%s is piao end' %self.name) 19 20 p=Piao('p1') 21 p.start() 22 print('开始') 23 print(p.pid) #查看pid
d.进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的。
1 #多进程共享一个打印终端(用python2测试看两个进程同时往一个终端打印,出现打印到一行的错误) 2 from multiprocessing import Process 3 import time 4 class Logger(Process): 5 def __init__(self): 6 super(Logger,self).__init__() 7 def run(self): 8 print(self.name) 9 10 11 for i in range(1000000): 12 l=Logger() 13 l.start()
1 #多进程共享一套文件系统 2 from multiprocessing import Process 3 import time,random 4 5 def work(f,msg): 6 f.write(msg) 7 f.flush() 8 9 10 f=open('a.txt','w') #在windows上无法把f当做参数传入,可以传入一个文件名,然后在work内用a+的方式打开文件,进行写入测试 11 for i in range(5): 12 p=Process(target=work,args=(f,str(i))) 13 p.start()
4、队列:进程间通信(IPC)之一
进程彼此之间互相隔离,要实现进程间通信,即IPC,multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
1.方法条用队列使用
调用方法:
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
参数说明:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
a.主要方法
q.put 方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get 方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait(): 同q.get(False)
q.put_nowait(): 同q.put(False)
q.empty(): 调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full(): 调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize(): 返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
b.其他方法
q.cancel_join_thread(): 不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close(): 关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
q.join_thread(): 连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
1)使用示例:
1 ''' 2 multiprocessing模块支持进程间通信的两种主要形式:管道和队列 3 都是基于消息传递实现的,但是队列接口 4 ''' 5 6 from multiprocessing import Process,Queue 7 import time 8 q=Queue(3) 9 10 11 #put ,get ,put_nowait,get_nowait,full,empty 12 q.put(3) 13 q.put(3) 14 q.put(3) 15 print(q.full()) #满了 16 17 print(q.get()) 18 print(q.get()) 19 print(q.get()) 20 print(q.empty()) #空了
2)生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
1 from multiprocessing import Process,Queue 2 import time,random,os 3 4 def consumer(q): 5 while True: 6 time.sleep(random.randint(1,3)) 7 res=q.get() 8 print('\033[45m消费者拿到了:%s\033[0m' %res) 9 10 def producer(seq,q): 11 for item in seq: 12 time.sleep(random.randint(1,3)) 13 print('\033[46m生产者生产了:%s\033[0m' %item) 14 15 q.put(item) 16 17 if __name__ == '__main__': 18 q=Queue() 19 20 seq=('包子%s' %i for i in range(10)) 21 c=Process(target=consumer,args=(q,)) 22 c.start() 23 producer(seq,q) 24 25 print('主线程')
1 from multiprocessing import Process,Queue 2 import time,random,os 3 4 5 def consumer(q): 6 while True: 7 time.sleep(random.randint(1,3)) 8 res=q.get() 9 if res is None:break 10 print('\033[45m消费者拿到了:%s\033[0m' %res) 11 12 def producer(seq,q): 13 for item in seq: 14 time.sleep(random.randint(1,3)) 15 print('\033[46m生产者生产了:%s\033[0m' %item) 16 17 q.put(item) 18 q.put(None) 19 20 if __name__ == '__main__': 21 q=Queue() 22 23 c=Process(target=consumer,args=(q,)) 24 c.start() 25 26 producer(('包子%s' %i for i in range(10)),q) 27 28 c.join() #主进程等待p结束,p等待c把数据都取完,c一旦取完数据,p.join就是不再阻塞,进 29 # 而主进程结束,主进程结束会回收守护进程c,而且c此时也没有存在的必要了 30 print('主线程')
2.类条用的队列使用方法
调用方法:
JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
参数说明:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
1 from multiprocessing import Process,JoinableQueue 2 import time,random 3 def consumer(q): 4 while True: 5 # time.sleep(random.randint(1,2)) 6 res=q.get() 7 print('消费者拿到了 %s' %res) 8 q.task_done() 9 10 11 def producer(seq,q): 12 for item in seq: 13 # time.sleep(random.randrange(1,2)) 14 q.put(item) 15 print('生产者做好了 %s' %item) 16 q.join() 17 18 if __name__ == '__main__': 19 q=JoinableQueue() 20 seq=('包子%s' %i for i in range(10)) 21 22 p=Process(target=consumer,args=(q,)) 23 p.daemon=True #设置为守护进程,在主线程停止时p也停止,但是不用担心,producer内调用q.join保证了consumer已经处理完队列中的所有元素 24 p.start() 25 26 producer(seq,q) 27 28 print('主线程')
ps.其他示例:
一个生产者+多个消费者
1 from multiprocessing import Process,JoinableQueue 2 import time,random 3 def consumer(name,q): 4 while True: 5 time.sleep(random.randint(1,2)) 6 res=q.get() 7 print('\033[45m%s拿到了 %s\033[0m' %(name,res)) 8 q.task_done() 9 10 11 def producer(seq,q): 12 for item in seq: 13 time.sleep(random.randrange(1,2)) 14 q.put(item) 15 print('\033[46m生产者做好了 %s\033[0m' %item) 16 q.join() 17 18 if __name__ == '__main__': 19 q=JoinableQueue() 20 seq=('包子%s' %i for i in range(10)) 21 22 p1=Process(target=consumer,args=('消费者1',q,)) 23 p2=Process(target=consumer,args=('消费者2',q,)) 24 p3=Process(target=consumer,args=('消费者3',q,)) 25 p1.daemon=True 26 p2.daemon=True 27 p3.daemon=True 28 p1.start() 29 p2.start() 30 p3.start() 31 32 producer(seq,q) 33 34 print('主线程')
子进程当生产者
1 from multiprocessing import Process,JoinableQueue 2 import time,random 3 def consumer(name,q): 4 while True: 5 # time.sleep(random.randint(1,2)) 6 res=q.get() 7 print('\033[45m%s拿到了 %s\033[0m' %(name,res)) 8 q.task_done() 9 10 11 def producer(seq,q): 12 for item in seq: 13 # time.sleep(random.randrange(1,2)) 14 q.put(item) 15 print('\033[46m生产者做好了 %s\033[0m' %item) 16 q.join() 17 18 if __name__ == '__main__': 19 q=JoinableQueue() 20 seq=['包子%s' %i for i in range(10)] #在windows下无法传入生成器,我们可以用列表解析测试 21 22 p1=Process(target=consumer,args=('消费者1',q,)) 23 p2=Process(target=consumer,args=('消费者2',q,)) 24 p3=Process(target=consumer,args=('消费者3',q,)) 25 p1.daemon=True 26 p2.daemon=True 27 p3.daemon=True 28 p1.start() 29 p2.start() 30 p3.start() 31 32 # producer(seq,q) #也可以是下面三行的形式,开启一个新的子进程当生产者,不用主线程当生产者 33 p4=Process(target=producer,args=(seq,q)) 34 p4.start() 35 p4.join() 36 print('主线程')
5、管道:进程间通信(IPC)之二
管道也可以说是队列的另外一种形式,下面我们就开始介绍基于管道实现金城之间的消息传递
调用方法:
Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
参数说明:
dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。
方法介绍:
a.主要方法:
conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
b. 其他方法:
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异常。
1)基于管道实现进程间通信(与队列的方式是类似的,队列就是管道加锁实现的)
1 from multiprocessing import Process,Pipe 2 3 import time,os 4 def consumer(p,name): 5 left,right=p 6 left.close() 7 while True: 8 try: 9 baozi=right.recv() 10 print('%s 收到包子:%s' %(name,baozi)) 11 except EOFError: 12 right.close() 13 break 14 def producer(seq,p): 15 left,right=p 16 right.close() 17 for i in seq: 18 left.send(i) 19 # time.sleep(1) 20 else: 21 left.close() 22 if __name__ == '__main__': 23 left,right=Pipe() 24 25 c1=Process(target=consumer,args=((left,right),'c1')) 26 c1.start() 27 28 29 seq=(i for i in range(10)) 30 producer(seq,(left,right)) 31 32 right.close() 33 left.close() 34 35 c1.join() 36 print('主进程')
ps.注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。
2) 管道可以用于双向通信,利用通常在客户端/服务器中使用的请求/响应模型或远程过程调用,就可以使用管道编写与进程交互的程序,如下
1 from multiprocessing import Process,Pipe 2 3 import time,os 4 def adder(p,name): 5 server,client=p 6 client.close() 7 while True: 8 try: 9 x,y=server.recv() 10 except EOFError: 11 server.close() 12 break 13 res=x+y 14 server.send(res) 15 print('server done') 16 if __name__ == '__main__': 17 server,client=Pipe() 18 19 c1=Process(target=adder,args=((server,client),'c1')) 20 c1.start() 21 22 server.close() 23 24 client.send((10,20)) 25 print(client.recv()) 26 client.close() 27 28 c1.join() 29 print('主进程')
ps.注意:send()和recv()方法使用pickle模块对对象进行序列化。
6、共享数据:进程间通信(IPC)之三
展望未来,基于消息传递的并发编程是大势所趋
即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合
通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,
还可以扩展到分布式系统中
进程间通信应该尽量避免使用本节所讲的共享数据的方式
进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此
1 from multiprocessing import Process,Manager 2 import os 3 4 def foo(name,d,l): 5 l.append(os.getpid()) 6 d[name]=os.getpid() 7 if __name__ == '__main__': 8 with Manager() as manager: 9 d=manager.dict({'name':'egon'}) 10 l=manager.list(['init',]) 11 12 p_l=[] 13 for i in range(10): 14 p=Process(target=foo,args=('p%s' %i,d,l)) 15 p.start() 16 p_l.append(p) 17 18 for p in p_l: 19 p.join() #必须有join不然会报错 20 21 print(d) 22 print(l)
7、进程同步(锁):
模拟抢票(Lock互斥锁)
1 #文件db的内容为:{"count":1} 2 #注意一定要用双引号,不然json无法识别 3 from multiprocessing import Process,Lock 4 import json 5 import time 6 import random 7 import os 8 9 def work(filename,lock): #买票 10 # lock.acquire() 11 with lock: 12 with open(filename,encoding='utf-8') as f: 13 dic=json.loads(f.read()) 14 # print('剩余票数: %s' % dic['count']) 15 if dic['count'] > 0: 16 dic['count']-=1 17 time.sleep(random.randint(1,3)) #模拟网络延迟 18 with open(filename,'w',encoding='utf-8') as f: 19 f.write(json.dumps(dic)) 20 print('%s 购票成功' %os.getpid()) 21 else: 22 print('%s 购票失败' %os.getpid()) 23 # lock.release() 24 25 if __name__ == '__main__': 26 lock=Lock() 27 p_l=[] 28 for i in range(100): 29 p=Process(target=work,args=('db',lock)) 30 p_l.append(p) 31 p.start() 32 for p in p_l: 33 p.join() 34 35 print('主线程')
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去,如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。一旦释放,就有人可以获得一把锁信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
8、信号量:
1 from multiprocessing import Process,Semaphore 2 import time,random 3 4 def go_wc(sem,user): 5 sem.acquire() 6 print('%s 占到一个茅坑' %user) 7 time.sleep(random.randint(0,3)) #模拟每个人拉屎速度不一样,0代表有的人蹲下就起来了 8 sem.release() 9 10 if __name__ == '__main__': 11 sem=Semaphore(5) 12 p_l=[] 13 for i in range(13): 14 p=Process(target=go_wc,args=(sem,'user%s' %i,)) 15 p.start() 16 p_l.append(p) 17 18 for i in p_l: 19 i.join() 20 print('============》')
9、事件
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
1 #_*_coding:utf-8_*_ 2 #!/usr/bin/env python 3 4 from multiprocessing import Process,Event 5 import time,random 6 7 def car(e,n): 8 while True: 9 if not e.is_set(): #Flase 10 print('\033[31m红灯亮\033[0m,car%s等着' %n) 11 e.wait() 12 print('\033[32m车%s 看见绿灯亮了\033[0m' %n) 13 time.sleep(random.randint(3,6)) 14 if not e.is_set(): 15 continue 16 print('走你,car', n) 17 break 18 19 def police_car(e,n): 20 while True: 21 if not e.is_set(): 22 print('\033[31m红灯亮\033[0m,car%s等着' % n) 23 e.wait(1) 24 print('灯的是%s,警车走了,car %s' %(e.is_set(),n)) 25 break 26 27 def traffic_lights(e,inverval): 28 while True: 29 time.sleep(inverval) 30 if e.is_set(): 31 e.clear() #e.is_set() ---->False 32 else: 33 e.set() 34 35 if __name__ == '__main__': 36 e=Event() 37 # for i in range(10): 38 # p=Process(target=car,args=(e,i,)) 39 # p.start() 40 41 for i in range(5): 42 p = Process(target=police_car, args=(e, i,)) 43 p.start() 44 t=Process(target=traffic_lights,args=(e,10)) 45 t.start() 46 47 print('============》')
10、进程池
开多进程的目的是为了并发,如果有多核,通常有几个核就开几个进程,进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行),但很明显需要并发执行的任务要远大于核数,这时我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数...
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
而且对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。
使用方法:
Pool([numprocess [,initializer [, initargs]]]):创建进程池
参数介绍:
numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
initializer:是每个工作进程启动时要执行的可调用对象,默认为None
initargs:是要传给initializer的参数组
方法介绍:
a.主要方法:
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.terminate(): 立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
p.jion(): 等待所有工作进程退出。此方法只能在close()或teminate()之后调用
b.其他方法:
方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
obj.ready():如果调用完成,返回True
obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
obj.wait([timeout]):等待结果变为可用。
1)使用进程池维护固定数目的进程
1 #Pool内的进程数默认是cpu核数,假设为4(查看方法os.cpu_count()) 2 #开启6个客户端,会发现2个客户端处于等待状态 3 #在每个进程内查看pid,会发现pid使用为4个,即多个客户端公用4个进程 4 from socket import * 5 from multiprocessing import Pool 6 import os 7 8 server=socket(AF_INET,SOCK_STREAM) 9 server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 10 server.bind(('127.0.0.1',8080)) 11 server.listen(5) 12 13 def talk(conn,client_addr): 14 print('进程pid: %s' %os.getpid()) 15 while True: 16 try: 17 msg=conn.recv(1024) 18 if not msg:break 19 conn.send(msg.upper()) 20 except Exception: 21 break 22 23 if __name__ == '__main__': 24 p=Pool() 25 while True: 26 conn,client_addr=server.accept() 27 p.apply_async(talk,args=(conn,client_addr)) 28 # p.apply(talk,args=(conn,client_addr)) #同步的话,则同一时间只有一个客户端能访问
1 from socket import * 2 3 client=socket(AF_INET,SOCK_STREAM) 4 client.connect(('127.0.0.1',8080)) 5 6 7 while True: 8 msg=input('>>: ').strip() 9 if not msg:continue 10 11 client.send(msg.encode('utf-8')) 12 msg=client.recv(1024) 13 print(msg.decode('utf-8'))
2)异步(非阻塞,apply_async)
1 #coding: utf-8 2 from multiprocessing import Process,Pool 3 import time 4 5 def func(msg): 6 print( "msg:", msg) 7 time.sleep(1) 8 return 'hahaha' 9 10 if __name__ == "__main__": 11 pool = Pool(processes = 3) 12 res_l=[] 13 for i in range(10): 14 msg = "hello %d" %(i) 15 res=pool.apply_async(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 16 res_l.append(res) 17 print("==============================>") 18 pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 19 pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 20 print("Sub-process(es) done.") 21 for i in res_l: 22 print(res.get())
3)同步(阻塞,apply)
1 #coding: utf-8 2 from multiprocessing import Process,Pool 3 import time 4 5 def func(msg): 6 print( "msg:", msg) 7 time.sleep(0.1) 8 return 'hahaha' 9 10 if __name__ == "__main__": 11 pool = Pool(processes = 3) 12 res_l=[] 13 for i in range(10): 14 msg = "hello %d" %(i) 15 res=pool.apply(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 16 res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个 17 print("==============================>") 18 pool.close() 19 pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 20 print("Sub-process(es) done.") 21 print(res_l) 22 for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法 23 print(res)
4)多进程池
1 #coding: utf-8 2 import multiprocessing 3 import os, time, random 4 5 def Lee(): 6 print("\nRun task Lee-%s" %(os.getpid())) #os.getpid()获取当前的进程的ID 7 start = time.time() 8 time.sleep(random.random() * 10) #random.random()随机生成0-1之间的小数 9 end = time.time() 10 print('Task Lee, runs %0.2f seconds.' %(end - start)) 11 12 def Marlon(): 13 print("\nRun task Marlon-%s" %(os.getpid())) 14 start = time.time() 15 time.sleep(random.random() * 40) 16 end=time.time() 17 print('Task Marlon runs %0.2f seconds.' %(end - start)) 18 19 def Allen(): 20 print("\nRun task Allen-%s" %(os.getpid())) 21 start = time.time() 22 time.sleep(random.random() * 30) 23 end = time.time() 24 print('Task Allen runs %0.2f seconds.' %(end - start)) 25 26 def Frank(): 27 print("\nRun task Frank-%s" %(os.getpid())) 28 start = time.time() 29 time.sleep(random.random() * 20) 30 end = time.time() 31 print('Task Frank runs %0.2f seconds.' %(end - start)) 32 33 def Egon(): 34 print("\nRun task Egon-%s" %(os.getpid())) 35 start = time.time() 36 time.sleep(random.random() * 20) 37 end = time.time() 38 print('Task Egon runs %0.2f seconds.' %(end - start)) 39 40 def Lily(): 41 print("\nRun task Lily-%s" %(os.getpid())) 42 start = time.time() 43 time.sleep(random.random() * 20) 44 end = time.time() 45 print('Task Lily runs %0.2f seconds.' %(end - start)) 46 47 if __name__=='__main__': 48 function_list= [Lee, Marlon, Allen, Frank, Egon, Lily] 49 print("parent process %s" %(os.getpid())) 50 51 pool=multiprocessing.Pool(4) 52 for func in function_list: 53 pool.apply_async(func) #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中 54 55 print('Waiting for all subprocesses done...') 56 pool.close() 57 pool.join() #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束 58 print('All subprocesses done.')
11、回调函数
1 from multiprocessing import Pool 2 import time,random 3 4 def get_page(url): 5 time.sleep(random.randint(1,3)) 6 print('下载页面: %s' %url) 7 return {'url':url} #模拟下载后的结果 8 9 def parse_page(page_content): 10 time.sleep(1) 11 print('解析页面: %s' %page_content) 12 13 14 if __name__ == '__main__': 15 urls=[ 16 'http://maoyan.com/board/7', 17 'http://maoyan.com/board/1', 18 'http://maoyan.com/board/2' 19 ] 20 p=Pool() 21 res_l=[] 22 for url in urls: 23 res=p.apply_async(get_page,args=(url,),callback=parse_page) 24 res_l.append(res) 25 26 for i in res_l: 27 i.get()
爬虫案例:
1 from multiprocessing import Pool 2 import time,random 3 import requests 4 import re 5 6 def get_page(url,pattern): 7 response=requests.get(url) 8 if response.status_code == 200: 9 return (response.text,pattern) 10 11 def parse_page(info): 12 page_content,pattern=info 13 res=re.findall(pattern,page_content) 14 for item in res: 15 dic={ 16 'index':item[0], 17 'title':item[1], 18 'actor':item[2].strip()[3:], 19 'time':item[3][5:], 20 'score':item[4]+item[5] 21 22 } 23 print(dic) 24 if __name__ == '__main__': 25 pattern1=re.compile(r'<dd>.*?board-index.*?>(\d+)<.*?title="(.*?)".*?star.*?>(.*?)<.*?releasetime.*?>(.*?)<.*?integer.*?>(.*?)<.*?fraction.*?>(.*?)<',re.S) 26 27 url_dic={ 28 'http://maoyan.com/board/7':pattern1, 29 } 30 31 p=Pool() 32 res_l=[] 33 for url,pattern in url_dic.items(): 34 res=p.apply_async(get_page,args=(url,pattern),callback=parse_page) 35 res_l.append(res) 36 37 for i in res_l: 38 i.get() 39 40 # res=requests.get('http://maoyan.com/board/7') 41 # print(re.findall(pattern,res.text))