第 10 章 python进程与多进程

1、背景知识html

顾明思义,进程即正在执行的一个过程,进程是对正在云的程序的一个抽象。python

进程的概念起源与操做系统,是操做系统最核心的概念,也是操做系统提供的最古老也是最重要的抽象概念之一,操做系统的其余全部内容都是围绕进程的概念展开的。linux

ps:即便能够利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力,将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。nginx

必备的理论基础:git

 1 #一 操做系统的做用:
 2     1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
 3     2:管理、调度进程,而且将多个进程对硬件的竞争变得有序
 4 
 5 #二 多道技术:
 6     1.产生背景:针对单核,实现并发
 7     ps:
 8     如今的主机通常是多核,那么每一个核都会利用多道技术
 9     有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再从新调度,会被调度到4个
10     cpu中的任意一个,具体由操做系统调度算法决定。
11     
12     2.空间上的复用:如内存中同时有多道程序
13     3.时间上的复用:复用一个cpu的时间片
14        强调:遇到io切,占用cpu时间过长也切,核心在于切以前将进程的状态保存下来,这样
15             才能保证下次切换回来时,能基于上次切走的位置继续运行

2、python并发编程之进程github

一、进程:正在进行的一个过程或者说一个任务,二负责执行任务则是cpu。web

 举例(单核+多道,实现多个进程的并发执行):

 egon在一个时间段内有不少任务要作:python备课的任务,写书的任务,交女友的任务,王者荣耀上分的任务,  

但egon同一时刻只能作一个任务(cpu同一时间只能干一个活),如何才能玩出多个任务并发执行的效果?

egon备一会课,再去跟李杰的女友聊聊天,再去打一会王者荣耀....这就保证了每一个任务都在进行中.

二、进程与程序的区别算法

程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。shell

举例:

想象一位有一手好厨艺的计算机科学家egon正在为他的女儿元昊烘制生日蛋糕。

他有作生日蛋糕的食谱,

厨房里有所需的原料:面粉、鸡蛋、韭菜,蒜泥等。

在这个比喻中:

    作蛋糕的食谱就是程序(即用适当形式描述的算法)

    计算机科学家就是处理器(cpu)

    而作蛋糕的各类原料就是输入数据。

   进程就是厨师阅读食谱、取来各类原料以及烘制蛋糕等一系列动做的总和。

 

如今假设计算机科学家egon的儿子alex哭着跑了进来,说:XXXXXXXXXXXXXX。

科学家egon想了想,处理儿子alex蛰伤的任务比给女儿元昊作蛋糕的任务更重要,因而

计算机科学家就记录下他照着食谱作到哪儿了(保存进程的当前状态),而后拿出一本急救手册,按照其中的指示处理蛰伤。这里,咱们看处处理机从一个进程(作蛋糕)切换到另外一个高优先级的进程(实施医疗救治),每一个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完以后,这位计算机科学家又回来作蛋糕,从他
离开时的那一步继续作下去。

须要强调的是:同一个程序执行两次,那也是两个进程,好比打开暴风影音,虽然都是同一个软件,可是一个能够播放一个视频。数据库

三、并发与并行

不管是并行仍是并发,在用户看来都是‘同时’运行的,无论是进程仍是线程,都只是一个任务而已,真是干活的是cpu,cpu来作这些任务,而一个cpu同一时刻只能执行衣蛾任务。

并发:是伪并行,即看起来是同时运行,单个cpu+多道技术就能够实现并发,(并行也属于并发)

并行:同时运行,只有具有多个cpu才能实现并行

单核下,能够利用多道技术,多个核,每一个核也均可以利用多道技术(多道技术就针对单核而言的)

有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,一旦任务1遇到i/o就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术,而一旦任务1的i/o结束了,操做系统会从新调用它(须要进程的调度,分配给那个cpu运行,由操做系统说了算),可能被分配给四个cpu中的任意一个去执行

全部现代计算机常常会在同一时间作不少件事,一个用户的pc(不管是单cpu仍是多cpu),均可以同时运行多个任务(一个任务能够理解为一个程序)

    启动一个进程来杀毒(360软件)

    启动一个进程来看电影(暴风影音)

    启动一个进程来聊天(腾讯QQ)

全部的这些进程都需被管理,因而一个支持多进程的多道程序系统是相当重要的

多道技术概念回顾:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另一个,使每一个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却能够运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操做系统的真正硬件并行(多个cpu共享同一个物理内存)

四、同步与异步

同步执行:一个进程在执行某个任务时,另一个进程必须等待其执行完毕,才能继续执行。

异步执行:一个进程在执行某个任务时,另一个进程无需等待其执行完毕,就能够继续执行,当有消息返回时,系统会通知进行处理,这样能够提升执行效率。

举个例子,打电话时就是同步通讯,发短信时就是异步通讯。

五、进程的建立

但凡是硬件,都须要有操做系统去管理,只要有操做系统,就有进程的概念,就须要有建立进程的方式,一些操做系统只为一个应用程序设计,好比微波炉中的控制器,一旦启动微波炉,全部的进程都已经存在。

  而对于通用系统(跑不少应用程序),须要有系统运行过程当中建立或撤销进程的能力,主要分为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系统来讲,从一开始父进程与子进程的地址空间就是不一样的。

六、进程的终止

 1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

 2. 出错退出(自愿,python a.py中a.py不存在)

 3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,能够捕捉异常,try...except...)

 4. 被其余进程杀死(非自愿,如kill -9)

 七、进程的层次结构

  不管UNIX仍是windows,进程只有一个父进程,不一样的是:

  1. 在UNIX中全部的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的全部成员。

  2. 在windows中,没有进程层次的概念,全部的进程都是地位相同的,惟一相似于进程层次的暗示,是在建立进程时,父进程获得一个特别的令牌(称为句柄),该句柄能够用来控制子进程,可是父进程有权把该句柄传给其余子进程,这样就没有层次了

八、进程的状态

  tail -f access.log |grep '404'

  执行程序tail,开启一个子进程,执行程序grep,开启另一个子进程,两个进程之间基于管道'|'通信,将tail的结果做为grep的输入。

  进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都没法运行

  其实在两种状况下会致使一个进程在逻辑上不能运行,

  1. 进程挂起是自身缘由,遇到I/O阻塞,便要让出CPU让其余进程去执行,这样保证CPU一直在工做

  2. 与进程无关,是操做系统层面,可能会由于一个进程占用时间过多,或者优先级等缘由,而调用其余的进程去使用CPU。

  于是一个进程由三种状态

九、进程并发的实现

进程并发的实如今于,硬件中断一个正在运行的进程,把此时进程运行的全部状态保存下来,为此,操做系统维护一张表格,即进程表(process table),每一个进程占用一个进程表项(这些表项也称为进程控制块)

该表存放了进程状态的重要信息:程序计数器,堆栈指针,内存分配情况,全部打开文件的状态,账号和调度信息,以及其余在进程由运行状态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过同样。

3、python并发编程之进程

一、multiprocessing模块介绍

python中多线程没法利用多核优点,若是想要充分地使用多核cpu的资源(os.cpu_count()),在python中大部分状况须要使用多进程,python提供了multiprocessing。

multiprocessing模块用来开启子进程,并在子进程中执行咱们定制的任务(好比函数),该模块与多线程模块threading的编程接口相似。

multiprocessing模块的功能众多:支持子进程,通讯和共享数据,执行不一样形式的同步,提供了process、Queue、Lock等组件。

须要再次强调的一点是:与线程不一样,进程没有任何共享状态,进程修改的数据,改动仅限与该进程内。

二、process类的介绍

建立进程的类:

1 Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化获得的对象,表示一个子进程中的任务(还没有启动)
2 
3 强调:
4 1. 须要使用关键字的方式来指定参数
5 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:

1 group参数未使用,值始终为None
2 
3 target表示调用对象,即子进程要执行的任务
4 
5 args表示调用对象的位置参数元组,args=(1,2,'egon',)
6 
7 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
8 
9 name为子进程的名称

方法介绍:

1 p.start():启动进程,并调用该子进程中的p.run() 
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,咱们自定义类的类中必定要实现该方法  
3 
4 p.terminate():强制终止进程p,不会进行任何清理操做,若是p建立了子进程,该子进程就成了僵尸进程,使用该方法须要特别当心这种状况。若是p还保存了一个锁那么也将不会被释放,进而致使死锁
5 p.is_alive():若是p仍然运行,返回True
6 
7 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 
3 p.name:进程的名称
4 
5 p.pid:进程的pid
6 
7 p.exitcode:进程在运行时为None、若是为–N,表示被信号N结束(了解便可)
8 
9 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络链接的底层进程间通讯提供安全性,这类链接只有在具备相同的身份验证键时才能成功(了解便可)

4、process类的使用

注意:在windows中process()必须放到 #  if __name__ == '__main__':下

 1 Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module. 
 2 If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources). 
 3 This is the reason for hiding calls to Process() inside
 4 
 5 if __name__ == "__main__"
 6 since statements inside this if-statement will not get called upon import.
 7 因为Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 
 8 若是在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 
 9 这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
10 
11 详细解释
详细解释

建立并开启子进程的两种方式

 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('主线程')
22 
23 方法一
方法一
 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('egon')
18 p2=Piao('alex')
19 p3=Piao('wupeiqi')
20 p4=Piao('yuanhao')
21 
22 p1.start() #start会自动调用run
23 p2.start()
24 p3.start()
25 p4.start()
26 print('主线程')
27 
28 方法二
方法二

练习1:socket通讯变成并发的形式

 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()
23 
24 server端
server端
 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'))
14 
15 from socket import *
16 
17 client=socket(AF_INET,SOCK_STREAM)
18 client.connect(('127.0.0.1',8080))
19 
20 
21 while True:
22     msg=input('>>: ').strip()
23     if not msg:continue
24 
25     client.send(msg.encode('utf-8'))
26     msg=client.recv(1024)
27     print(msg.decode('utf-8'))
28 
29 多个client端
多个client端
每来一个客户端,都在服务端开启一个进程,若是并发来一个万个客户端,要开启一万个进程吗,你本身尝试着在你本身的机器上开启一万个,10万个进程试一试。
解决方法:进程池
这样实现会有问题?

process对象的join方法

 1 from multiprocessing import Process
 2 import time
 3 import random
 4 
 5 class Piao(Process):
 6     def __init__(self,name):
 7         self.name=name
 8         super().__init__()
 9     def run(self):
10         print('%s is piaoing' %self.name)
11         time.sleep(random.randrange(1,3))
12         print('%s is piao end' %self.name)
13 
14 
15 p=Piao('egon')
16 p.start()
17 p.join(0.0001) #等待p中止,等0.0001秒就再也不等了
18 print('开始')
19 
20 join:主进程等,等待子进程结束
join:主进程等,等待子进程结束
 1 from multiprocessing import Process
 2 import time
 3 import random
 4 def piao(name):
 5     print('%s is piaoing' %name)
 6     time.sleep(random.randint(1,3))
 7     print('%s is piao end' %name)
 8 
 9 p1=Process(target=piao,args=('egon',))
10 p2=Process(target=piao,args=('alex',))
11 p3=Process(target=piao,args=('yuanhao',))
12 p4=Process(target=piao,args=('wupeiqi',))
13 
14 p1.start()
15 p2.start()
16 p3.start()
17 p4.start()
18 
19 #有的同窗会有疑问:既然join是等待进程结束,那么我像下面这样写,进程不就又变成串行的了吗?
20 #固然不是了,必须明确:p.join()是让谁等?
21 #很明显p.join()是让主线程等待p的结束,卡住的是主线程而绝非进程p,
22 
23 #详细解析以下:
24 #进程只要start就会在开始运行了,因此p1-p4.start()时,系统中已经有四个并发的进程了
25 #而咱们p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键
26 #join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其他p2,p3,p4仍然在运行,等#p1.join结束,可能p2,p3,p4早已经结束了,这样p2.join,p3.join.p4.join直接经过检测,无需等待
27 # 因此4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间
28 p1.join()
29 p2.join()
30 p3.join()
31 p4.join()
32 
33 print('主线程')
34 
35 
36 #上述启动进程与join进程能够简写为
37 # p_l=[p1,p2,p3,p4]
38 # 
39 # for p in p_l:
40 #     p.start()
41 # 
42 # for p in p_l:
43 #     p.join()
44 
45 有了join,程序不就是串行了吗???
有了join,程序不就是串行了吗???

process对象的其余方法或属性

 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
25 
26 terminate与is_alive
terminate与is_alive
 1 from multiprocessing import Process
 2 import time
 3 import random
 4 class Piao(Process):
 5     def __init__(self,name):
 6         # self.name=name
 7         # super().__init__() #Process的__init__方法会执行self.name=Piao-1,
 8         #                    #因此加到这里,会覆盖咱们的self.name=name
 9 
10         #为咱们开启的进程设置名字的作法
11         super().__init__()
12         self.name=name
13 
14     def run(self):
15         print('%s is piaoing' %self.name)
16         time.sleep(random.randrange(1,3))
17         print('%s is piao end' %self.name)
18 
19 p=Piao('egon')
20 p.start()
21 print('开始')
22 print(p.pid) #查看pid
23 
24 name与pid
name与pid

5、守护进程

主进程建立守护进程

主进程建立守护进程

其一:守护进程会在主进程代码执行后结束就终止

其二:守护进程内没法再开启子进程,不然抛出异常:AssertionError:daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止。

 1 from multiprocessing import Process
 2 import time
 3 import random
 4 
 5 class Piao(Process):
 6     def __init__(self,name):
 7         self.name=name
 8         super().__init__()
 9     def run(self):
10         print('%s is piaoing' %self.name)
11         time.sleep(random.randrange(1,3))
12         print('%s is piao end' %self.name)
13 
14 
15 p=Piao('egon')
16 p.daemon=True #必定要在p.start()前设置,设置p为守护进程,禁止p建立子进程,而且父进程代码执行结束,p即终止运行
17 p.start()
18 print('')
View Code
 1 #主进程代码运行完毕,守护进程就会结束
 2 from multiprocessing import Process
 3 from threading import Thread
 4 import time
 5 def foo():
 6     print(123)
 7     time.sleep(1)
 8     print("end123")
 9 
10 def bar():
11     print(456)
12     time.sleep(3)
13     print("end456")
14 
15 
16 p1=Process(target=foo)
17 p2=Process(target=bar)
18 
19 p1.daemon=True
20 p1.start()
21 p2.start()
22 print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123,由于主进程打印main----时,p1也执行了,可是随即被终止
23 
24 迷惑人的例子
迷惑人的例子

6、进程同步(锁)

进程之间数据不共享,可是共享同一套文件系统,因此访问同一个文件,或同一个打印终端,是没有问题,竞争带来的结果就是错乱,如何控制,就是加锁处理

part1:多个进程共享同一打印终端

 1 #并发运行,效率高,但竞争同一打印终端,带来了打印错乱
 2 from multiprocessing import Process
 3 import os,time
 4 def work():
 5     print('%s is running' %os.getpid())
 6     time.sleep(2)
 7     print('%s is done' %os.getpid())
 8 
 9 if __name__ == '__main__':
10     for i in range(3):
11         p=Process(target=work)
12         p.start()
13 
14 并发运行,效率高,但竞争同一打印终端,带来了打印错乱
并发运行,效率高,但竞争同一打印终端,带来了打印错乱
 1 #由并发变成了串行,牺牲了运行效率,但避免了竞争
 2 from multiprocessing import Process,Lock
 3 import os,time
 4 def work(lock):
 5     lock.acquire()
 6     print('%s is running' %os.getpid())
 7     time.sleep(2)
 8     print('%s is done' %os.getpid())
 9     lock.release()
10 if __name__ == '__main__':
11     lock=Lock()
12     for i in range(3):
13         p=Process(target=work,args=(lock,))
14         p.start()
15 
16 加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争
加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争

part2:多个进程共享同一文件

文件当数据库,模拟抢票

 1 #文件db的内容为:{"count":1}
 2 #注意必定要用双引号,否则json没法识别
 3 from multiprocessing import Process,Lock
 4 import time,json,random
 5 def search():
 6     dic=json.load(open('db.txt'))
 7     print('\033[43m剩余票数%s\033[0m' %dic['count'])
 8 
 9 def get():
10     dic=json.load(open('db.txt'))
11     time.sleep(0.1) #模拟读数据的网络延迟
12     if dic['count'] >0:
13         dic['count']-=1
14         time.sleep(0.2) #模拟写数据的网络延迟
15         json.dump(dic,open('db.txt','w'))
16         print('\033[43m购票成功\033[0m')
17 
18 def task(lock):
19     search()
20     get()
21 if __name__ == '__main__':
22     lock=Lock()
23     for i in range(100): #模拟并发100个客户端抢票
24         p=Process(target=task,args=(lock,))
25         p.start()
26 
27 并发运行,效率高,但竞争写同一文件,数据写入错乱
并发运行,效率高,但竞争写同一文件,数据写入错乱
 1 #文件db的内容为:{"count":1}
 2 #注意必定要用双引号,否则json没法识别
 3 from multiprocessing import Process,Lock
 4 import time,json,random
 5 def search():
 6     dic=json.load(open('db.txt'))
 7     print('\033[43m剩余票数%s\033[0m' %dic['count'])
 8 
 9 def get():
10     dic=json.load(open('db.txt'))
11     time.sleep(0.1) #模拟读数据的网络延迟
12     if dic['count'] >0:
13         dic['count']-=1
14         time.sleep(0.2) #模拟写数据的网络延迟
15         json.dump(dic,open('db.txt','w'))
16         print('\033[43m购票成功\033[0m')
17 
18 def task(lock):
19     search()
20     lock.acquire()
21     get()
22     lock.release()
23 if __name__ == '__main__':
24     lock=Lock()
25     for i in range(100): #模拟并发100个客户端抢票
26         p=Process(target=task,args=(lock,))
27         p.start()
28 
29 加锁:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全
加锁:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全

总结;

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

虽然能够用文件共享数据实现进程间通讯,但问题是:

一、效率低

二、须要本身加锁处理

 

为此multiprocessing模块为咱们提供了基于消息的IPC通讯机制:队列和管道。

一、队列和管道都是将数据存放与内存中。

二、队列又是基于(管道+锁)实现的,可让咱们从复杂的锁问题中解脱出来。

咱们应该尽可能避免使用共享数据,尽量使用消息传递和队列,避免处理复杂的同步和锁问题,并且在进程数目增多时,每每能够得到更好的可获展性。

7、队列(推荐使用)

进程彼此之间互相隔离,要实现进程间通讯(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

建立队列的类(底层就是以管道和锁定的方式实现):

1 Queue([maxsize]):建立共享的进程队列,Queue是多进程安全的队列,可使用Queue实现多进程之间的数据传递。

参数介绍:

maxsize是队列中容许最大项数,省略则无大小限制。

方法介绍:

主要方法:

1 q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。若是blocked为True(默认值),而且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。若是超时,会抛出Queue.Full异常。若是blocked为False,但该Queue已满,会当即抛出Queue.Full异常。
2 q.get方法能够从队列读取而且删除一个元素。一样,get方法有两个可选参数:blocked和timeout。若是blocked为True(默认值),而且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。若是blocked为False,有两种状况存在,若是Queue有一个值可用,则当即返回该值,不然,若是队列为空,则当即抛出Queue.Empty异常.
3  
4 q.get_nowait():同q.get(False)
5 q.put_nowait():同q.put(False)
6 
7 q.empty():调用此方法时q为空则返回True,该结果不可靠,好比在返回True的过程当中,若是队列中又加入了项目。
8 q.full():调用此方法时q已满则返回True,该结果不可靠,好比在返回True的过程当中,若是队列中的项目被取走。
9 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()同样

 其余方法:

1 q.cancel_join_thread():不会在进程退出时自动链接后台线程。能够防止join_thread()方法阻塞
2 q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但还没有写入的数据,但将在此方法完成时立刻关闭。若是q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,若是某个使用者正在被阻塞在get()操做上,关闭生产者中的队列不会致使get()方法返回错误。
3 q.join_thread():链接队列的后台线程。此方法用于在调用q.close()方法以后,等待全部队列项被消耗。默认状况下,此方法由不是q的原始建立者的全部进程调用。调用q.cancel_join_thread方法能够禁止这种行为

应用:

 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()) #空了
View Code

8、生产者消费者模型

生产者消费者模型

在并发编程中使用生产者和消费者模式可以解决大多数并发问题。该模式经过生产线程和消费线程的工做能力来提升程序的总体处理数据的速度。

为何要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,若是生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据,一样的道理,若是消费者的处理能力大于生产者,那么消费者就必须等待生产者,为了解决这个问题因而引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是经过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通信,而经过阻塞队列来进行通信,因此生产者生产完数据以后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就至关于一个缓冲区,平衡了生产者和消费者的处理能力。

基于队列实现生产者消费者模型

 1 from multiprocessing import Process,Queue
 2 import time,random,os
 3 def consumer(q):
 4     while True:
 5         res=q.get()
 6         time.sleep(random.randint(1,3))
 7         print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 8 
 9 def producer(q):
10     for i in range(10):
11         time.sleep(random.randint(1,3))
12         res='包子%s' %i
13         q.put(res)
14         print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
15 
16 if __name__ == '__main__':
17     q=Queue()
18     #生产者们:即厨师们
19     p1=Process(target=producer,args=(q,))
20 
21     #消费者们:即吃货们
22     c1=Process(target=consumer,args=(q,))
23 
24     #开始
25     p1.start()
26     c1.start()
27     print('')
View Code

此时的问题是主进程永远不会结束,缘由是:生产者p在生产完后就结束了,可是消费者c在取空了q后,则一直处于死循环中且卡在q.get()这一步,

解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号就能够break出死循环。

 1 from multiprocessing import Process,Queue
 2 import time,random,os
 3 def consumer(q):
 4     while True:
 5         res=q.get()
 6         if res is None:break #收到结束信号则结束
 7         time.sleep(random.randint(1,3))
 8         print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 9 
10 def producer(q):
11     for i in range(10):
12         time.sleep(random.randint(1,3))
13         res='包子%s' %i
14         q.put(res)
15         print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
16     q.put(None) #发送结束信号
17 if __name__ == '__main__':
18     q=Queue()
19     #生产者们:即厨师们
20     p1=Process(target=producer,args=(q,))
21 
22     #消费者们:即吃货们
23     c1=Process(target=consumer,args=(q,))
24 
25     #开始
26     p1.start()
27     c1.start()
28     print('')
29 
30 生产者在生产完毕后发送结束信号None
生产者在生产完毕后发送结束信号None

注意:结束信号None,不必定要由生产者发,主进程一样能够发,但主进程须要等生产者结束后才应该发送该信号。

 1 from multiprocessing import Process,Queue
 2 import time,random,os
 3 def consumer(q):
 4     while True:
 5         res=q.get()
 6         if res is None:break #收到结束信号则结束
 7         time.sleep(random.randint(1,3))
 8         print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 9 
10 def producer(q):
11     for i in range(2):
12         time.sleep(random.randint(1,3))
13         res='包子%s' %i
14         q.put(res)
15         print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
16 
17 if __name__ == '__main__':
18     q=Queue()
19     #生产者们:即厨师们
20     p1=Process(target=producer,args=(q,))
21 
22     #消费者们:即吃货们
23     c1=Process(target=consumer,args=(q,))
24 
25     #开始
26     p1.start()
27     c1.start()
28 
29     p1.join()
30     q.put(None) #发送结束信号
31     print('')
32 
33 主进程在生产者生产完毕后发送结束信号None
主进程在生产者生产完毕后发送结束信号None

但上述解决方式,在有多个生产者和多个消费者时,咱们则须要用一个很low的方式去解决。

 1 from multiprocessing import Process,Queue
 2 import time,random,os
 3 def consumer(q):
 4     while True:
 5         res=q.get()
 6         if res is None:break #收到结束信号则结束
 7         time.sleep(random.randint(1,3))
 8         print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 9 
10 def producer(name,q):
11     for i in range(2):
12         time.sleep(random.randint(1,3))
13         res='%s%s' %(name,i)
14         q.put(res)
15         print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
16 
17 
18 
19 if __name__ == '__main__':
20     q=Queue()
21     #生产者们:即厨师们
22     p1=Process(target=producer,args=('包子',q))
23     p2=Process(target=producer,args=('骨头',q))
24     p3=Process(target=producer,args=('泔水',q))
25 
26     #消费者们:即吃货们
27     c1=Process(target=consumer,args=(q,))
28     c2=Process(target=consumer,args=(q,))
29 
30     #开始
31     p1.start()
32     p2.start()
33     p3.start()
34     c1.start()
35 
36     p1.join() #必须保证生产者所有生产完毕,才应该发送结束信号
37     p2.join()
38     p3.join()
39     q.put(None) #有几个生产者就应该发送几回结束信号None
40     q.put(None) #发送结束信号
41     q.put(None) #发送结束信号
42     print('')
43 
44 有几个生产者就须要发送几回结束信号:至关low
有几个生产者就须要发送几回结束信号:至关low

其实咱们的思路无非是发送结束信号而已,有另一种队列提供了这种机制

1 #JoinableQueue([maxsize]):这就像是一个Queue对象,但队列容许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
2 
3    #参数介绍:
4     maxsize是队列中容许最大项数,省略则无大小限制。    
5   #方法介绍:
6     JoinableQueue的实例p除了与Queue对象相同的方法以外还具备:
7     q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。若是调用此方法的次数大于从队列中删除项目的数量,将引起ValueError异常
8     q.join():生产者调用此方法进行阻塞,直到队列中全部的项目均被处理。阻塞将持续到队列中的每一个项目均调用q.task_done()方法为止
 1 from multiprocessing import Process,JoinableQueue
 2 import time,random,os
 3 def consumer(q):
 4     while True:
 5         res=q.get()
 6         time.sleep(random.randint(1,3))
 7         print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 8 
 9         q.task_done() #向q.join()发送一次信号,证实一个数据已经被取走了
10 
11 def producer(name,q):
12     for i in range(10):
13         time.sleep(random.randint(1,3))
14         res='%s%s' %(name,i)
15         q.put(res)
16         print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
17     q.join()
18 
19 
20 if __name__ == '__main__':
21     q=JoinableQueue()
22     #生产者们:即厨师们
23     p1=Process(target=producer,args=('包子',q))
24     p2=Process(target=producer,args=('骨头',q))
25     p3=Process(target=producer,args=('泔水',q))
26 
27     #消费者们:即吃货们
28     c1=Process(target=consumer,args=(q,))
29     c2=Process(target=consumer,args=(q,))
30     c1.daemon=True
31     c2.daemon=True
32 
33     #开始
34     p_l=[p1,p2,p3,c1,c2]
35     for p in p_l:
36         p.start()
37 
38     p1.join()
39     p2.join()
40     p3.join()
41     print('') 
42     
43     #主进程等--->p1,p2,p3等---->c1,c2
44     #p1,p2,p3结束了,证实c1,c2确定全都收完了p1,p2,p3发到队列的数据
45     #于是c1,c2也没有存在的价值了,应该随着主进程的结束而结束,因此设置成守护进程
View Code

9、管道

进程间通讯(IPC)方式二:管道(不推荐使用,了解便可)

 1 #建立管道的类:
 2 Pipe([duplex]):在进程之间建立一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的链接对象,强调一点:必须在产生Process对象以前产生管道
 3 #参数介绍:
 4 dumplex:默认管道是全双工的,若是将duplex射成False,conn1只能用于接收,conn2只能用于发送。
 5 #主要方法:
 6     conn1.recv():接收conn2.send(obj)发送的对象。若是没有消息可接收,recv方法会一直阻塞。若是链接的另一端已经关闭,那么recv方法会抛出EOFError。
 7     conn1.send(obj):经过链接发送对象。obj是与序列化兼容的任意对象
 8  #其余方法:
 9 conn1.close():关闭链接。若是conn1被垃圾回收,将自动调用此方法
10 conn1.fileno():返回链接使用的整数文件描述符
11 conn1.poll([timeout]):若是链接上的数据可用,返回True。timeout指定等待的最长时限。若是省略此参数,方法将当即返回结果。若是将timeout射成None,操做将无限期地等待数据到达。
12  
13 conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。若是进入的消息,超过了这个最大值,将引起IOError异常,而且在链接上没法进行进一步读取。若是链接的另一端已经关闭,不再存在任何数据,将引起EOFError异常。
14 conn.send_bytes(buffer [, offset [, size]]):经过链接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,而后调用c.recv_bytes()函数进行接收    
15  
16 conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或相似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。若是消息长度大于可用的缓冲区空间,将引起BufferTooShort异常。
17 
18 介绍
介绍
 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('主进程')
37 
38 基于管道实现进程间通讯(与队列的方式是相似的,队列就是管道加锁实现的)
基于管道实现进程间通讯(与队列的方式是相似的,队列就是管道加锁实现的)

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

 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('主进程')
30 #注意:send()和recv()方法使用pickle模块对对象进行序列化。
31 
32 管道能够用于双向通讯,利用一般在客户端/服务器中使用的请求/响应模型或远程过程调用,就可使用管道编写与进程交互的程序
管道能够用于双向通讯,利用一般在客户端/服务器中使用的请求/响应模型或远程过程调用,就可使用管道编写与进程交互的程序

10、共享数据

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

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

经过消息队列交换数据,这样极大地减小使用锁定好其余同步手段的需求。

还能够扩展到分布式系统中

进程间通讯应该尽可能避免使用共享数据的方式

进程间数据是独立的,能够借助于队列或管道实现通讯,两者都是基于消息传递的

虽然进程间数据独立,但能够经过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,
 1 from multiprocessing import Manager,Process,Lock
 2 import os
 3 def work(d,lock):
 4     # with lock: #不加锁而操做共享的数据,确定会出现数据错乱
 5         d['count']-=1
 6 
 7 if __name__ == '__main__':
 8     lock=Lock()
 9     with Manager() as m:
10         dic=m.dict({'count':100})
11         p_l=[]
12         for i in range(100):
13             p=Process(target=work,args=(dic,lock))
14             p_l.append(p)
15             p.start()
16         for p in p_l:
17             p.join()
18         print(dic)
19         #{'count': 94}
20 
21 进程之间操做共享的数据
进程之间操做共享的数据

11、进程池

在利用python进行系统管理的时候,特别是同时操做多个文件目录,或者远程控制多台主机,并行操做能够节约大量的时间,多进程是实现并发的手段之一,须要注意的问题是:

一、很明显须要并发执行的任务一般要远大于核数

二、一个操做系统不可能无限开启进程,一般有几个核就开几个进程

三、进程开启过多,效率反而会降低(开启进程是须要占用系统资源的,并且开启多余核数数目的进程也没法作到并行)

例如当被操做对象数目不大时,能够直接利用multiprocessing中process动态生成多个进程,司机十几个还好,但若是是上百个,上千个......手动的去限制进程数量却又太过繁琐,此时能够发挥进程池的功效。

咱们就能够经过维护一个进程池来控制进程数目,好比httpd的进程模式,规定最小进程数和最大进程数。

ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool能够提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,若是池尚未满,那么就会建立一个新的进程用来执行该请求;但若是池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。

建立进程池的类:若是指定numprocess为3,则进程池会从无到有建立三个进程,而后自始至终使用这个三个进程去执行全部任务,不会开启其余进程。

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

参数介绍:

1 numprocess:要建立的进程数,若是省略,将默认使用cpu_count()的值
2 initializer:是每一个工做进程启动时要执行的可调用对象,默认为None
3 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():关闭进程池,防止进一步操做。若是全部操做持续挂起,它们将在工做进程终止前完成
P.jion():等待全部工做进程退出。此方法只能在close()或teminate()以后调用

其余方法:

1 方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具备如下方法
2 obj.get():返回结果,若是有必要则等待结果到达。timeout是可选的。若是在指定时间内尚未到达,将引起一场。若是远程操做中引起了异常,它将在调用此方法时再次被引起。
3 obj.ready():若是调用完成,返回True
4 obj.successful():若是调用完成且没有引起异常,返回True,若是在结果就绪以前调用此方法,引起异常
5 obj.wait([timeout]):等待结果变为可用。
6 obj.terminate():当即终止全部工做进程,同时不执行任何清理或结束任何挂起工做。若是p被垃圾回收,将自动调用此函数
View Code

应用:

 1 from multiprocessing import Pool
 2 import os,time
 3 def work(n):
 4     print('%s run' %os.getpid())
 5     time.sleep(3)
 6     return n**2
 7 
 8 if __name__ == '__main__':
 9     p=Pool(3) #进程池中从无到有建立三个进程,之后一直是这三个进程在执行任务
10     res_l=[]
11     for i in range(10):
12         res=p.apply(work,args=(i,)) #同步运行,阻塞、直到本次任务执行完毕拿到res
13         res_l.append(res)
14     print(res_l)
15 
16 apply同步执行:阻塞式
apply同步执行:阻塞式
 1 from multiprocessing import Pool
 2 import os,time
 3 def work(n):
 4     print('%s run' %os.getpid())
 5     time.sleep(3)
 6     return n**2
 7 
 8 if __name__ == '__main__':
 9     p=Pool(3) #进程池中从无到有建立三个进程,之后一直是这三个进程在执行任务
10     res_l=[]
11     for i in range(10):
12         res=p.apply_async(work,args=(i,)) #同步运行,阻塞、直到本次任务执行完毕拿到res
13         res_l.append(res)
14 
15     #异步apply_async用法:若是使用异步提交的任务,主进程须要使用jion,等待进程池内任务都处理完,而后能够用get收集结果,不然,主进程结束,进程池可能还没来得及执行,也就跟着一块儿结束了
16     p.close()
17     p.join()
18     for res in res_l:
19         print(res.get()) #使用get来获取apply_aync的结果,若是是apply,则没有get方法,由于apply是同步执行,马上获取结果,也根本无需get
20 
21 apply_async异步执行:非阻塞
apply_async异步执行:非阻塞
 1 #一:使用进程池(非阻塞,apply_async)
 2 #coding: utf-8
 3 from multiprocessing import Process,Pool
 4 import time
 5 
 6 def func(msg):
 7     print( "msg:", msg)
 8     time.sleep(1)
 9     return msg
10 
11 if __name__ == "__main__":
12     pool = Pool(processes = 3)
13     res_l=[]
14     for i in range(10):
15         msg = "hello %d" %(i)
16         res=pool.apply_async(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
17         res_l.append(res)
18     print("==============================>") #没有后面的join,或get,则程序总体结束,进程池中的任务还没来得及所有执行完也都跟着主进程一块儿结束了
19 
20     pool.close() #关闭进程池,防止进一步操做。若是全部操做持续挂起,它们将在工做进程终止前完成
21     pool.join()   #调用join以前,先调用close函数,不然会出错。执行完close后不会有新的进程加入到pool,join函数等待全部子进程结束
22 
23     print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证实结果已经计算完毕,剩下的事情就是调用每一个对象下的get方法去获取结果
24     for i in res_l:
25         print(i.get()) #使用get来获取apply_aync的结果,若是是apply,则没有get方法,由于apply是同步执行,马上获取结果,也根本无需get
26 
27 #二:使用进程池(阻塞,apply)
28 #coding: utf-8
29 from multiprocessing import Process,Pool
30 import time
31 
32 def func(msg):
33     print( "msg:", msg)
34     time.sleep(0.1)
35     return msg
36 
37 if __name__ == "__main__":
38     pool = Pool(processes = 3)
39     res_l=[]
40     for i in range(10):
41         msg = "hello %d" %(i)
42         res=pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
43         res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另一个
44     print("==============================>")
45     pool.close()
46     pool.join()   #调用join以前,先调用close函数,不然会出错。执行完close后不会有新的进程加入到pool,join函数等待全部子进程结束
47 
48     print(res_l) #看到的就是最终的结果组成的列表
49     for i in res_l: #apply是同步的,因此直接获得结果,没有get()方法
50         print(i)
51 
52 详解:apply_async与apply
详解:apply_async与apply

练习2:使用进程池维护固定数目的进程

 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)) #同步的话,则同一时间只有一个客户端能访问
29 
30  server端
server端
 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'))
14 
15 客户端
客户端

发现:并发开启多个客户端,服务端同一时间只有3个不一样的pid,干掉一个客户端,另一个客户端才会进来,被3个进程之一处理。

12、进程池之回调函数

须要回调函数的场景:进程池中任何一个任务一旦处理完了,就当即告知主进程:我好了,你能够处理个人结果了,主进程则调用一个函数去处理该结果,该函数即回调函数。

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

 1 from multiprocessing import Pool
 2 import requests
 3 import json
 4 import os
 5 
 6 def get_page(url):
 7     print('<进程%s> get %s' %(os.getpid(),url))
 8     respone=requests.get(url)
 9     if respone.status_code == 200:
10         return {'url':url,'text':respone.text}
11 
12 def pasrse_page(res):
13     print('<进程%s> parse %s' %(os.getpid(),res['url']))
14     parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
15     with open('db.txt','a') as f:
16         f.write(parse_res)
17 
18 
19 if __name__ == '__main__':
20     urls=[
21         'http://www.baidu.com',
22         'http://www.python.org',
23         'http://www.openstack.org',
24         'http://help.github.com/',
25         'http://www.sina.com.cn/'
26     ]
27 
28     p=Pool(3)
29     res_l=[]
30     for url in urls:
31         res=p.apply_async(get_page,args=(url,),callback=pasrse_page)
32         res_l.append(res)
33 
34     p.close()
35     p.join()
36     print([res.get() for res in res_l]) #拿到的是get_page的结果,其实彻底不必拿该结果,该结果已经传给回调函数处理了
37 
38 '''
39 打印结果:
40 <进程3388> get https://www.baidu.com
41 <进程3389> get https://www.python.org
42 <进程3390> get https://www.openstack.org
43 <进程3388> get https://help.github.com/
44 <进程3387> parse https://www.baidu.com
45 <进程3389> get http://www.sina.com.cn/
46 <进程3387> parse https://www.python.org
47 <进程3387> parse https://help.github.com/
48 <进程3387> parse http://www.sina.com.cn/
49 <进程3387> parse https://www.openstack.org
50 [{'url': 'https://www.baidu.com', 'text': '<!DOCTYPE html>\r\n...',...}]
51 '''
View Code
 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))
42 
43 爬虫案例
爬虫案例

若是在主进程中等待进程池中全部都执行完毕后,再统一处理结果,则无需回调函数

 1 from multiprocessing import Pool
 2 import time,random,os
 3 
 4 def work(n):
 5     time.sleep(1)
 6     return n**2
 7 if __name__ == '__main__':
 8     p=Pool()
 9 
10     res_l=[]
11     for i in range(10):
12         res=p.apply_async(work,args=(i,))
13         res_l.append(res)
14 
15     p.close()
16     p.join() #等待进程池中全部进程执行完毕
17 
18     nums=[]
19     for res in res_l:
20         nums.append(res.get()) #拿到全部结果
21     print(nums) #主进程拿到全部的处理结果,能够在主进程中进行统一进行处理
View Code

十3、paramiko模块

一、介绍:

paramiko是一个用于作远程控制的模块,使用该模块能够对远程服务器进行命令或文件操做,值得一说的是,fabric和ansible内部的远程管理就是使用的paramiko来现实。

二、下载安装

pip3 install paramiko #在python3中
1 pycrypto,因为 paramiko 模块内部依赖pycrypto,因此先下载安装pycrypto #在python2中
2 pip3 install pycrypto
3 pip3 install paramiko
4 注:若是在安装pycrypto2.0.1时发生以下错误
5         command 'gcc' failed with exit status 1...
6 多是缺乏python-dev安装包致使
7 若是gcc没有安装,请事先安装gcc
8 
9 在python2中
在python2中

三、使用

sshclient

用于链接远程服务器并执行基本命令

基于用户名密码链接:

 1 import paramiko
 2 
 3 # 建立SSH对象
 4 ssh = paramiko.SSHClient()
 5 # 容许链接不在know_hosts文件中的主机
 6 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 7 # 链接服务器
 8 ssh.connect(hostname='120.92.84.249', port=22, username='root', password='xxx')
 9 
10 # 执行命令
11 stdin, stdout, stderr = ssh.exec_command('df')
12 # 获取命令结果
13 result = stdout.read()
14 print(result.decode('utf-8'))
15 # 关闭链接
16 ssh.close()
 1 import paramiko
 2 
 3 transport = paramiko.Transport(('120.92.84.249', 22))
 4 transport.connect(username='root', password='xxx')
 5 
 6 ssh = paramiko.SSHClient()
 7 ssh._transport = transport
 8 
 9 stdin, stdout, stderr = ssh.exec_command('df')
10 res=stdout.read()
11 print(res.decode('utf-8'))
12 
13 transport.close()
14 
15 SSHClient 封装 Transport
SSHClient 封装 Transport

基于公钥密钥链接:

客户端文件名:id_rsa

服务端必须有文件名:authorized_keys(在用ssh-keygen时,必须制做一个zuthorized_keys,能够用ssh-copy-id来制做)

 1 import paramiko
 2 
 3 private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
 4 
 5 # 建立SSH对象
 6 ssh = paramiko.SSHClient()
 7 # 容许链接不在know_hosts文件中的主机
 8 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 9 # 链接服务器
10 ssh.connect(hostname='120.92.84.249', port=22, username='root', pkey=private_key)
11 
12 # 执行命令
13 stdin, stdout, stderr = ssh.exec_command('df')
14 # 获取命令结果
15 result = stdout.read()
16 print(result.decode('utf-8'))
17 # 关闭链接
18 ssh.close()
View Code
 1 import paramiko
 2 
 3 private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
 4 
 5 transport = paramiko.Transport(('120.92.84.249', 22))
 6 transport.connect(username='root', pkey=private_key)
 7 
 8 ssh = paramiko.SSHClient()
 9 ssh._transport = transport
10 
11 stdin, stdout, stderr = ssh.exec_command('df')
12 result=stdout.read()
13 print(result.decode('utf-8'))
14 
15 transport.close()
16 
17 SSHClient 封装 Transport
SSHClient 封装 Transport
 1 import paramiko
 2 from io import StringIO
 3 
 4 key_str = """-----BEGIN RSA PRIVATE KEY-----
 5 MIIEoQIBAAKCAQEAsJmFLrSeCumJvga0Gl5O5wVOVwMIy2MpqIyQPi5J87dg89a4
 6 Da9fczJog7qoSbRwHFOQoCHNphSlp5KPhGsF6RJewkIw9H1UKV4dCOyl/4HOAkAD
 7 rKrsEDmrJ9JlzF2GTTZSnTgVQWcvBS2RKB4eM2R9aJ11xV6X2Hk4YDLTExIWeabb
 8 h2TUKw0iyjI8pRuYLKkF2X16u9TBwfOTroGYgiNFHQvhsQppbEbI49NF2XkCkFMi
 9 8/7tLjf95InE/VUUq56JqfzyHwdpHou+waXbwtvGgXN3sz+KkuEv6R2qDz06upZV
10 FCZRRpDhzoR8Uh/UEzTGZb8z7FB6EJXUiXJikQIBIwKCAQBBmBuGYFf1bK+BGG7H
11 9ySe81ecqVsJtx4aCFLVRGScWg4RbQKIvXs5an6XU/VdNGQnx0RYvBkvDvuzRRC8
12 J8Bd4kB0CfTtGJuaVigKoQp02HEWx1HSa17+tlWD0c4KFBvwywi+DYQ83S64x8gz
13 eOalX9bPFenqORPUD8R7gJeKvPVc6ZTPeorpuH7u9xayP0Eop8qKxZza9Xh3foVj
14 Qo4IxoYnDN57CIRX5PFSlDDggpmr8FtRF4nAxmFq8LhSp05ivzX/Ku1SNHdaMWZO
15 7va8tISXdLI5m0EGzoVoBvohIbwlxI6kfmamrh6Eas2Jnsc4CLzMsR4jBWt0LHLv
16 /SLnAoGBANaEUf/Jptab9G/xD9W2tw/636i3gLpTPY9KPtCcAxqStNeT6RAWZ5HF
17 lKJg+NKpu3pI45ldAwvts0i+aCZk2xakEWIZWqCmXm31JSPDQTaMGe7H0vOmUaxx
18 ncdpBVdvhMbfFUgei15iKfuafgrKaS9oIkntXEgrC+3wBOI0Gbx3AoGBANLAGxAF
19 TK7ydr+Q1+6/ujs6e8WsXt8HZMa/1khCVSbrf1MgACvZPSSSrDpVwaDTSjlRI4AL
20 bb0l0RFU+/0caMiHilscuJdz9Fdd9Ux4pjROZa3TF5CFhvP7PsZAoxOo+yqJg4zr
21 996GG/aAv4M8lQJ2rDFk/Dgn5y/AaAun1oM3AoGAGIQmoOPYjY4qkHNSRE9lYOl4
22 pZFQilKn8x5tlC8WTC4GCgJGhX7nQ9wQ/J1eQ/YkDfmznH+ok6YjHkGlgLsRuXHW
23 GdcDCwuzBUCWh76LHC1EytUCKnloa3qy8jfjWnMlHgrd3FtDILrC+C7p1Vj2FAvm
24 qVz0moiTpioPL8twp9MCgYEAin49q3EyZFYwxwdpU7/SJuvq750oZq0WVriUINsi
25 A6IR14oOvbqkhb94fhsY12ZGt/N9uosq22H+anms6CicoQicv4fnBHDFI3hCHE9I
26 pgeh50GTJHUA6Xk34V2s/kp5KpThazv6qCw+QubkQExh660SEdSlvoCfPKMCi1EJ
27 TukCgYAZKY1NZ2bjJyyO/dfNvMQ+etUL/9esi+40GUGyJ7SZcazrN9z+DO0yL39g
28 7FT9NMIc2dsmNJQMaGBCDl0AjO1O3b/wqlrNvNBGkanxn2Htn5ajfo+LBU7yHAcV
29 7w4X5HLarXiE1mj0LXFKJhdvFqU53KUQJXBqR6lsMqzsdPwLMJg==
30 -----END RSA PRIVATE KEY-----"""
31 
32 private_key = paramiko.RSAKey(file_obj=StringIO(key_str))
33 transport = paramiko.Transport(('120.92.84.249', 22))
34 transport.connect(username='root', pkey=private_key)
35 
36 ssh = paramiko.SSHClient()
37 ssh._transport = transport
38 
39 stdin, stdout, stderr = ssh.exec_command('df')
40 result = stdout.read()
41 print(result.decode('utf-8'))
42 transport.close()
43 
44 print(result)
45 
46 基于私钥字符串进行链接
基于私钥字符串进行链接

sftpclient

用于链接远程服务器并执行上传下载

基于用户名密码上传下载

 1 import paramiko
 2  
 3 transport = paramiko.Transport(('120.92.84.249',22))
 4 transport.connect(username='root',password='xxx')
 5  
 6 sftp = paramiko.SFTPClient.from_transport(transport)
 7 # 将location.py 上传至服务器 /tmp/test.py
 8 sftp.put('/tmp/id_rsa', '/etc/test.rsa')
 9 # 将remove_path 下载到本地 local_path
10 sftp.get('remove_path', 'local_path')
11  
12 transport.close()
View Code

基于公钥密钥上传下载

 1 import paramiko
 2 
 3 private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
 4 
 5 transport = paramiko.Transport(('120.92.84.249', 22))
 6 transport.connect(username='root', pkey=private_key )
 7 
 8 sftp = paramiko.SFTPClient.from_transport(transport)
 9 # 将location.py 上传至服务器 /tmp/test.py
10 sftp.put('/tmp/id_rsa', '/tmp/a.txt')
11 # 将remove_path 下载到本地 local_path
12 sftp.get('remove_path', 'local_path')
13 
14 transport.close()
View Code
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import paramiko
 4 import uuid
 5 
 6 class Haproxy(object):
 7 
 8     def __init__(self):
 9         self.host = '172.16.103.191'
10         self.port = 22
11         self.username = 'root'
12         self.pwd = '123'
13         self.__k = None
14 
15     def create_file(self):
16         file_name = str(uuid.uuid4())
17         with open(file_name,'w') as f:
18             f.write('sb')
19         return file_name
20 
21     def run(self):
22         self.connect()
23         self.upload()
24         self.rename()
25         self.close()
26 
27     def connect(self):
28         transport = paramiko.Transport((self.host,self.port))
29         transport.connect(username=self.username,password=self.pwd)
30         self.__transport = transport
31 
32     def close(self):
33 
34         self.__transport.close()
35 
36     def upload(self):
37         # 链接,上传
38         file_name = self.create_file()
39 
40         sftp = paramiko.SFTPClient.from_transport(self.__transport)
41         # 将location.py 上传至服务器 /tmp/test.py
42         sftp.put(file_name, '/home/root/tttttttttttt.py')
43 
44     def rename(self):
45 
46         ssh = paramiko.SSHClient()
47         ssh._transport = self.__transport
48         # 执行命令
49         stdin, stdout, stderr = ssh.exec_command('mv /home/root/tttttttttttt.py /home/root/ooooooooo.py')
50         # 获取命令结果
51         result = stdout.read()
52 
53 
54 ha = Haproxy()
55 ha.run()
56 
57 Demo
Demo
相关文章
相关标签/搜索