一、异常处理: |
<<<在程序运行过程中,总会遇到各种各样的错误。>>>
#有的错误是程序编写有问题造成的(例如:语法错误),这些错误应该在调试的过程中,就应该修改好的。有的错误是用户输入非要求的内容,(如:让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。)
#还有一类错误是完全无法在程序运行过程中预测到的,比如写入文件的时候,磁盘满了,写不进去了,或从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。
#Python内置了一套异常处理机制,来帮助我们进行错误处理。
#高级语言通常都内置了一套try...except...finally...的处理机制,python也不例外。
#常见的几种报错信息:
#列表的:IndexError,字典的:KeyError,数值的:1/0 ZeroDivisionError,数值与字符串相加的:TypeError
#以上的异常信息报错,我们可以用try...except...来进行捕捉及避免。
1 #try:
2 # # msg=input('>>:')
3 # # int(msg) #ValueError
4 #
5 # # print(x) #NameError
6 #
7 # d={'a':1}
8 # d['b'] #KeyError
9 #
10 # l=[1,2]
11 # l[10] #IndexError
12 #
13 # 1+'asdfsadfasdf' #TypeError
14 #
15 # except ValueError as e:
16 # print(e)
17 # except NameError:
18 # print('NameError')
19 # except KeyError as e:
20 # print(e)
21 #
22 # print('=============>')
23 # print('=============>')
24 # print('=============>')
#捕获取异常后打印异常的信息,并继续执行异常后面的代码
###另一种异常捕获,万能异常,不需要知道异常报错的内容,也可以捕获到任何异常信息。
# try....except Exception as e... print(e)
#Exception万能异常
# try:
# # msg=input('>>:')
# # int(msg) #ValueError
#
# # print(x) #NameError
# #
# # d={'a':1}
# # d['b'] #KeyError
# #
# # l=[1,2]
# # l[10] #IndexError
#
# 1+'asdfsadfasdf' #TypeError
#
# except Exception as e:
# print(e)
#
# print('=============>')
# print('=============>')
# print('=============>')
Exception万能异常
1 ###try...except...else...finally..的用法
2 # try:
3 # # msg=input('>>:')
4 # # int(msg) #ValueError
5 # #
6 # # print(x) #NameError
7 # #
8 # # d={'a':1}
9 # # d['b'] #KeyError
10 # #
11 # # l=[1,2]
12 # # l[10] #IndexError
13 # #
14 # # 1+'asdfsadfasdf' #TypeError
15 # print('aaaaaa')
16 #
17 # except ValueError as e:
18 # print(e)
19 # except NameError:
20 # print('NameError')
21 # except KeyError as e:
22 # print(e)
23 # except Exception as e:
24 # print('Exception======>',e)
25 # else:
26 # print('没有异常时发生会执行')
27 # finally:
28 # print('有没有异常都会执行')
29 #
30 #
31 # print('=============>')
32 # print('=============>')
33 # print('=============>')
<<<Exception无法匹配语法的异常,语法异常应该在程序运行前就解决掉>>>
二、进程 |
#进程介绍:
#现在的操作系统都是支持“多任务”的操作系统。
#所谓的“多任务”,就是操作系统可以同时运行多个任务。比如,你可以一边用浏览器上网,一边在用音乐播放器听歌,一边用Word写文档,这就是多任务,到时同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
#现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么单核CPU是怎么执行多任务的呢?
#答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,在切换到任务3上面,任务3在执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
#真正的并行执行多任务只能在多核CPU上实现,但由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
#对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个word就是启动了一个word进程。
#有些进程还不止同时干一件事,比如word,它可以同时进行打字,拼写检查,打印等等。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)
#每个进程至少要做一件事,所以一个进程至少有一个线程。当然像word这种复杂的进程,可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂的交替运行,看起来就像同时执行一样。
当然真正的同时执行多线程需要多核CPU才可能实现。
#Python程序如何执行多任务:
#一、启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
#二、启动一个进程,在一个进程内启动多个线程,这样多个线程也可以一块执行多个任务。
#三、启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模块会更复杂,实际应用中很少采用到的。
#总结:多任务实现的三种方式:
1>多进程模式。
2>多线程模式。
3>多进程+多线程模式。
<<<小结:
#线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
#多进程和多线程的程序涉及到同步、数据共享的问题、编写起来更复杂。>>>
三、进程的实现 |
1 ###要让Python程序实现多进程(multiprocessing),我们先了解操作系统的相关知识。
2 ##Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次返回一次,但fork()调用一次返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
3 ##子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
1 ###创建进程示例:<函数> 导入multiprocessing模块中的Process类
2 from multiprocessing import Process
3 import time
4 import random
5
6 def piao(name):
7 print('%s is piaoing' %name)
8 time.sleep(3)
9 print('%s is piao end' %name)
10
11 if __name__ == '__main__': #程序中首先执行__main__下的内容;
12 p1=Process(target=piao,args=('egon',),name='<p1>') #将Process的内容实例化给P1,target表示调用对象,args表示调用对象的位置参数元组(元组在括号里面加逗号,)。kwargs表示调用对象的字典。Name为别名(进程的别名)。Group实质上不使用。
13 p1.start() #表示开始调用
14 print('p1 name is %s ' %p1.name)
15 # time.sleep(1)
16 print('父进程')
1 #返回结果为: #<<< p1.start()去给子进程分配任务调度数据,执行完后,会让子进程执行它的内容,同时切换下一个任务,操作下面的内容,在执行过程中如果子进程已经执行完,会先执行子进程的,如果没有则还是执行主进程的内容。下面的结果是子进程还没执行完。
所以主进程会先执行完,然后等待子进程执行完毕。>>>
2 # p1 name is <p1>
3 # 父进程
4 # egon is piaoing
5 # egon is piao end
#创建进程示例<类> 定义类的时候必须定义一个run()方法
from multiprocessing import Process
import time
import random
class Piao(Process): #定义一下名为Piao的类,调用父类Process
def __init__(self,name): #定义一个初始化函数,
super().__init__() #用super()调用父类的__init__函数
self.name=name
def run(self): #定义一个run()函数
print('%s is piaoing' %self.name)
time.sleep(3)
print('%s is piao end' %self.name)
if __name__ == '__main__': #定义一个主函数
print('hahahahahahah=========>')
p1=Piao('egon')
p1.start() #p1.run #p1.start()开启一子进程,调用Piao的run方法
# time.sleep(1)
print('父进程')
1 ##返回结果: #执行主函数的内容,先打印,然后实例化Piao类,p1.start()开启一个子进程,系统会发出调度的命令,然后去执行下一步,然后执行主进程中的print语句,然后执行子进程中的第一个打印,然后睡3秒,在执行第二个打印语句。
2 # hahahahahahah=========>
3 # 父进程
4 # egon is piaoing
5 # egon is piao end
四、进程对象的方法和属性 |
1 #进程Process的属性有:
2 #start() : 开启一个子进程
3 #join() : 等子进程结束,
4 #is_alive(), 判断子进程是否存活
5 #daemon(), 开启守护进程,
6 # terminate(), 结束子进程。
1 #join()方法示例:
2 from multiprocessing import Process
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 if __name__ == '__main__':
10 p1=Process(target=piao,args=('张三',))
11 p2=Process(target=piao,args=('李四',))
12 p3=Process(target=piao,args=('王五',))
13 p4=Process(target=piao,args=('赵六',))
14
15 p_l=[p1,p2,p3,p4]
16 for p in p_l:
17 p.start() #创建子进程。
18 for p in p_l:
19 p.join() #等待子进程结束。
20 print('主进程')
#返回结果: #输入的结果顺序是随机的(由操作系统进行分配子进程,执行的先后顺序也是相当于随机的,谁先执行完,打印谁的信息,因为有调用join()属性,所以会执行完子进程后才会执行主进程。)
张三 is piaoing
李四 is piaoing
赵六 is piaoing
王五 is piaoing
赵六 is piao end
张三 is piao end
李四 is piao end
王五 is piao end
主进程
1 #terminate,daemon示例:
2 from multiprocessing import Process
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 if __name__ == '__main__':
10 p1=Process(target=piao,args=('egon',))
11 p1.daemon=True #daemon开启守护进程,后台运行,
12 p1.start()
13
14 p1.terminate() #结束子进程
15 print(p1.is_alive())
16 time.sleep(1)
17 print(p1.is_alive())
18
19 print('主进程')
20
21 print(p1.name)
22 print(p1.pid)
1 #返回的结果为: (实例化Process类(调用piao方法,args传值),然后开启守护进程(守护主进程,当主进程执行完后就不在执行子进程。),p1.start()开启子进程,子进程去调用piao函数的内容,接着去执行p1.terminate()结束子进程,
然后执行is_alive(),查询子进程是否存活,因为在发出结束子进程进执行的操作会需要一点时间,所以在第一次执行is_alive()时结果会是True,当sleep(1),睡了一秒后,在查询,is_alive()为False,然后执行下面主进程的打印语句)
2 True
3 False
4 主进程
5 Process-1
6 3396
7 #
from multiprocessing import Process
from socket import *
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):
while True: #通讯循环
try:
msg=conn.recv(1024)
if not msg:break
conn.send(msg.upper())
except Exception:
break
if __name__ == '__main__':
while True: #链接循环
conn,addr=server.accept()
p=Process(target=talk,args=(conn,addr))
p.start()
Socket并发服务端
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'))
Socket并发客户端
# from multiprocessing import Process
# import time,random
#
# def work(filename,msg):
# with open(filename,'a',encoding='utf-8') as f:
# f.write(msg)
#
# if __name__ == '__main__':
#
# for i in range(5):
# p=Process(target=work,args=('a.txt','进程%s\n' %str(i)))
# p.start()
多进程共享一套文件系统
五、队列和生产者消费者模型 |
1 ###队列:
2 #队列:是先进先出的
3 #需要导入Queue类: from multiprocessing import Process,Quess
4 #示例:
5
1 q=Queue(3) #创建三个队列,
2
3 q.put({'a':1}) #用put将{'a':1}放入队列中
4 q.put('b') #将'b' 放入队列中
5 q.put('c') #将'c' 放入队列中
6 print(q.full()) #用full()方法,判断队列是否已经满,返回True或False
7 q.put('d',False) #等同于q.put_nowait('d') #不等待直接放入队列,此时队列已经满,放入的话会报错,只能等待有队列被取出后才能放入。
8 q.put('d',timeout=2) #等待2秒放入队列,但队列已满,只能等有队列被取出后才能放入。
9 print(q.qsize()) #size() 统计队列的个数。
10
11 print(q.get()) #取出一个队列,第一个队列内容(先进先出)
12 print(q.get())
13 print(q.get())
14 print(q.empty()) #判断队列是否为空,返回True或False
15 print(q.get(block=False)) #不等待,直接取队列中的内容
16 print(q.get_nowait()) #不等待,直接取队列中的内容
17 print(q.get(timeout=2)) #等待2秒,取队列中的内容
1 ###生产者和消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。
同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,
而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
#示例:
2 from multiprocessing import Process,Queue #导入Process,Queue模块
3 import time #导入time模块
4 import random #导入random模块
5
6 def consumer(q,name): #定义一个消费者属性
7 while True:
8 time.sleep(random.randint(1,3))
9 res=q.get()
10 print('\033[41m消费者%s拿到了%s\033[0m' %(name,res))
11
12 def producer(seq,q,name): #定义一个生产者属性
13 for item in seq:
14 time.sleep(random.randint(1,3))
15 q.put(item)
16 print('\033[42m生产者%s生产了%s\033[0m' %(name,item))
17
18 if __name__ == '__main__':
19 q=Queue() #创建队列,
20 c=Process(target=consumer,args=(q,'egon'),) #实例化进程,调度consumer方法,传值,q,"egon"
21 c.start()
22
23 seq=['包子%s' %i for i in range(10)]
24 producer(seq,q,'厨师1')
25
26 print('主进程')
1 #返回结果为: 生产者生产完后,会放入队列中,然后等待消费者取出来,
2 生产者厨师1生产了包子0
3 消费者egon拿到了包子0
4 生产者厨师1生产了包子1
5 消费者egon拿到了包子1
6 生产者厨师1生产了包子2
7 消费者egon拿到了包子2
8 生产者厨师1生产了包子3
9 消费者egon拿到了包子3
10 生产者厨师1生产了包子4
11 消费者egon拿到了包子4
12 生产者厨师1生产了包子5
13 消费者egon拿到了包子5
14 生产者厨师1生产了包子6
15 消费者egon拿到了包子6
16 生产者厨师1生产了包子7
17 消费者egon拿到了包子7
18 生产者厨师1生产了包子8
19 生产者厨师1生产了包子9
20 主进程
21 消费者egon拿到了包子8
22 消费者egon拿到了包子9
# 创建队列的另外一个类:
JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
#参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
#方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process,JoinableQueue
import time,random
def consumer(name,q):
while True:
time.sleep(random.randint(1,2))
res=q.get()
print('\033[45m%s拿到了 %s\033[0m' %(name,res))
q.task_done()
def producer(seq,q):
for item in seq:
time.sleep(random.randrange(1,2))
q.put(item)
print('\033[46m生产者做好了 %s\033[0m' %item)
q.join()
if __name__ == '__main__':
q=JoinableQueue()
seq=('包子%s' %i for i in range(10))
p1=Process(target=consumer,args=('消费者1',q,))
p2=Process(target=consumer,args=('消费者2',q,))
p3=Process(target=consumer,args=('消费者3',q,))
p1.daemon=True
p2.daemon=True
p3.daemon=True
p1.start()
p2.start()
p3.start()
producer(seq,q)
print('主线程')
一个生产者+多个消费者
from multiprocessing import Process,JoinableQueue
import time
import random
def consumer(q,name):
while True:
# time.sleep(random.randint(1,3))
res=q.get()
q.task_done()
print('\033[37m消费者%s拿到了%s\033[0m' %(name,res))
def producer(seq,q,name):
for item in seq:
# time.sleep(random.randint(1,3))
q.put(item)
print('\033[36m生产者%s生产了%s\033[0m' %(name,item))
q.join()
print('============>>')
if __name__ == '__main__':
q=JoinableQueue()
c=Process(target=consumer,args=(q,'egon'),)
c.daemon=True #设置守护进程,主进程结束c就结束
c.start()
seq=['包子%s' %i for i in range(10)]
p=Process(target=producer,args=(seq,q,'厨师1'))
p.start()
# master--->producer----->q--->consumer(10次task_done)
p.join() #主进程等待p结束,p等待c把数据都取完,c一旦取完数据,p.join就是不再阻塞,进
# 而主进程结束,主进程结束会回收守护进程c,而且c此时也没有存在的必要了
print('主进程')
也可以开启一个新的子进程当生产者,不用主线程当生产者
六、进程间的通信之管道(了解) |
管道也可以说是队列的另外一种形式,下面我们就开始介绍基于管道实现金城之间的消息传递
#创建管道的类:
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
import time,os
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(10))
producer(seq,(left,right))
right.close()
left.close()
c1.join()
print('主进程')
注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。
###管道可以用于双向通信,利用通常在客户端/服务器中使用的请求/响应模型或远程过程调用,就可以使用管道编写与进程交互的程序,如下:
from multiprocessing import Process,Pipe
import time,os
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('主进程')
<<<注意:send()和recv()方法使用pickle模块对对象进行序列化。>>>
七、进程间通信方式三:共享数据(不推荐使用,了解即可) |
展望未来,基于消息传递的并发编程是大势所趋
即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合
通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,
还可以扩展到分布式系统中
进程间通信应该尽量避免使用本节所讲的共享数据的方式
进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过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. For example,
from multiprocessing import Process,Manager
import os
def foo(name,d,l):
l.append(os.getpid())
d[name]=os.getpid()
if __name__ == '__main__':
with Manager() as manager:
d=manager.dict({'name':'egon'})
l=manager.list(['init',])
p_l=[]
for i in range(10):
p=Process(target=foo,args=('p%s' %i,d,l))
p.start()
p_l.append(p)
for p in p_l:
p.join() #必须有join不然会报错
print(d)
print(l)
import threading
from threading import Thread
from multiprocessing import Process,Lock,Queue,JoinableQueue,Manager
def consumer(d,lock):
# with lock:
d['count']-=1
if __name__ == '__main__':
lock=Lock()
with Manager() as m:
d=m.dict({'count':100})
l=[]
for i in range(100):
p=Process(target=consumer,args=(d,lock))
l.append(p)
p.start()
for i in l:
i.join()
print(d)
print(threading.current_thread().getName())
使用共享数据也需要自己加锁处理
八、进程池 |
开多进程的目的是为了并发,如果有多核,通常有几个核就开几个进程,进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行),但很明显需要并发执行的任务要远大于核数,这时我们就可以通过维护一个进程池来控制进程数目,
比如httpd的进程模式,规定最小进程数和最大进程数...
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
而且对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,
就重用进程池中的进程。
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。
1 #创建进程池的类:
2 Pool([numprocess [,initializer [, initargs]]]):创建进程池
1 参数:
2 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
3 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
4 initargs:是要传给initializer的参数组
#方法介绍:
主要方法
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被垃圾回收,将自动调用此函数
应用
提交任务,并在主进程中拿到结果(之前的Process是执行任务,结果放到队列里,现在可以在主进程中直接拿到结果)
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)
#一:使用进程池(非阻塞,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)
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_async与apply
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('主进程')
进程同步之模拟抢票软件
九、回调函数 |
1 #不需要回调函数的场景:如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数
from multiprocessing import Pool
import time,random,os
def work(n):
time.sleep(1)
return n**2
if __name__ == '__main__':
p=Pool()
res_l=[]
for i in range(10):
res=p.apply_async(work,args=(i,))
res_l.append(res)
p.close()
p.join() #等待进程池中所有进程执行完毕
nums=[]
for res in res_l:
nums.append(res.get()) #拿到所有结果
print(nums) #主进程拿到所有的处理结果,可以在主进程中进行统一进行处理
1 #需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数
2
3 #我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。
from multiprocessing import Pool
import time,random,os
def get_page(url):
print('(进程 %s) 正在下载页面 %s' %(os.getpid(),url))
time.sleep(random.randint(1,3))
return url #用url充当下载后的结果
def parse_page(page_content):
print('<进程 %s> 正在解析页面: %s' %(os.getpid(),page_content))
time.sleep(1)
return '{%s 回调函数处理结果:%s}' %(os.getpid(),page_content)
if __name__ == '__main__':
urls=[
'http://maoyan.com/board/1',
'http://maoyan.com/board/2',
'http://maoyan.com/board/3',
'http://maoyan.com/board/4',
'http://maoyan.com/board/5',
'http://maoyan.com/board/7',
]
p=Pool()
res_l=[]
#异步的方式提交任务,然后把任务的结果交给callback处理
#注意:会专门开启一个进程来处理callback指定的任务(单独的一个进程,而且只有一个)
for url in urls:
res=p.apply_async(get_page,args=(url,),callback=parse_page)
res_l.append(res)
#异步提交完任务后,主进程先关闭p(必须先关闭),然后再用p.join()等待所有任务结束(包括callback)
p.close()
p.join()
print('{主进程 %s}' %os.getpid())
#收集结果,发现收集的是get_page的结果
#所以需要注意了:
#1. 当我们想要在将get_page的结果传给parse_page处理,那么就不需要i.get(),通过指定callback,就可以将i.get()的结果传给callback执行的任务
#2. 当我们想要在主进程中处理get_page的结果,那就需要使用i.get()获取后,再进一步处理
for i in res_l: #本例中,下面这两步是多余的
callback_res=i.get()
print(callback_res)
'''
打印结果:
(进程 52346) 正在下载页面 http://maoyan.com/board/1
(进程 52347) 正在下载页面 http://maoyan.com/board/2
(进程 52348) 正在下载页面 http://maoyan.com/board/3
(进程 52349) 正在下载页面 http://maoyan.com/board/4
(进程 52348) 正在下载页面 http://maoyan.com/board/5
<进程 52345> 正在解析页面: http://maoyan.com/board/3
(进程 52346) 正在下载页面 http://maoyan.com/board/7
<进程 52345> 正在解析页面: http://maoyan.com/board/1
<进程 52345> 正在解析页面: http://maoyan.com/board/2
<进程 52345> 正在解析页面: http://maoyan.com/board/4
<进程 52345> 正在解析页面: http://maoyan.com/board/5
<进程 52345> 正在解析页面: http://maoyan.com/board/7
{主进程 52345}
http://maoyan.com/board/1
http://maoyan.com/board/2
http://maoyan.com/board/3
http://maoyan.com/board/4
http://maoyan.com/board/5
http://maoyan.com/board/7
'''
from multiprocessing import Pool
import time,random
import requests
import re
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]
}
print(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()
# res=requests.get('http://maoyan.com/board/7')
# print(re.findall(pattern,res.text))
爬虫案例