第九章:Python多进程和paramiko模块
大纲:
1、paramiko模块
2、开启进程的两种方式
3、多进程实现并发的套接字通信
4、Process对象的join方法和其他属性或方法
5、守护进程
6、同步锁
7、进程之间通信
8、生产者消费者模型
9、进程池已经
10、进程池之回调函数
一、paramiko模块
paramiko是一个用于做远程控制的模块,使用该模块可以对远程服务器进行命令或文件操作,值得一说的是,fabric和ansible内部的远程管理就是使用的paramiko来现实。
下载安装:pip3 install paramiko #在python3中
pycrypto,由于 paramiko 模块内部依赖pycrypto,所以先下载安装pycrypto #在python2中 pip3 install pycrypto pip3 install paramiko 注:如果在安装pycrypto2.0.1时发生如下错误 command 'gcc' failed with exit status 1... 可能是缺少python-dev安装包导致 如果gcc没有安装,请事先安装gcc
ssh的使用
SSHClient
#基于用户名密码连接:
# import paramiko # # 创建SSH对象 # ssh = paramiko.SSHClient() # 允许连接不在know_hosts文件中的主机 # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # # 连接服务器 # # while True: # ssh.connect(hostname='10.0.0.20', port=22, username='root', password='123456') # # cmd=input('>>').strip() # # 执行命令 # stdin, stdout, stderr = ssh.exec_command(cmd) # # 获取命令结果 # result = stdout.read() # print(result.decode('utf-8'),end='') # # 关闭连接 # ssh.close()
基于公钥密钥连接:
客户端文件名:id_rsa
服务端必须有文件名:authorized_keys(在用ssh-keygen时,必须制作一个authorized_keys,可以用ssh-copy-id来制作)
# import paramiko # # private_key = paramiko.RSAKey.from_private_key_file('id_rsa') # # # 创建SSH对象 # ssh = paramiko.SSHClient() # # 允许连接不在know_hosts文件中的主机 # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # # while True: # # 连接服务器 # ssh.connect(hostname='10.0.0.20', port=22, username='root', pkey=private_key) # # cmd = input('>>').strip() # # 执行命令 # stdin, stdout, stderr = ssh.exec_command(cmd) # # 获取命令结果 # result = stdout.read() # print(result.decode('utf-8'),end='') # # 关闭连接 # ssh.close()
SFTPClient
用于连接远程服务器并执行上传下载
基于用户名密码上传下载
################ SFTPClient # import paramiko # # transport = paramiko.Transport(('10.0.0.20', 22)) # transport.connect(username='root', password='123456') # # sftp = paramiko.SFTPClient.from_transport(transport) # # 将location.py 上传至服务器 /tmp/test.py # sftp.put('id_rsa', '/tmp/id_rsa_test') # # 将remove_path 下载到本地 local_path # sftp.get('/tmp/AABB', 'AABB_test') # # transport.close()
基于公钥密钥上传下载
# import paramiko # # private_key = paramiko.RSAKey.from_private_key_file('id_rsa') # # transport = paramiko.Transport(('10.0.0.20', 22)) # transport.connect(username='root', pkey=private_key ) # # sftp = paramiko.SFTPClient.from_transport(transport) # # 将location.py 上传至服务器 /tmp/test.py # sftp.put('id_rsa', '/tmp/test.py') # # 将remove_path 下载到本地 local_path # sftp.get('/tmp/88', '8888') # # transport.close()
二、开启进程的两种方式
1、使用Process来启动进程
from multiprocessing import Process import os,sys,time def work(name): print('task is running >%s' %name) time.sleep(2) if __name__=='__main__': # Process(target=work,args=('sheng',), kwargs={'name':'sheng'}) p1=Process(target=work, args=('sheng',)) #在windows 下Process一定要写在__main__下面 p2= Process(target=work, args=('lele',)) p1.start() p2.start() print('主进程')
2、继承Process类来启动多进程
可以定义自己需要的属性。
from multiprocessing import Process import os,sys,time class Myprocess(Process): def __init__(self,name): super().__init__() #使用super可以防止继承的勒种有相同的属性 self.name=name def run(self): print('task is running >%s' % self.name) time.sleep(2) if __name__=='__main__': p=Myprocess('sheng') p.start() print('主进程')
三、多进程实现并发的套接字通信
multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
Process类的介绍:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. 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开启的进程
Process类的使用
注意:在windows中Process()必须放到# if __name__ == '__main__':下
服务端:
# -*- coding: utf-8 -*- __author__ = 'ShengLeQi' from socket import * from multiprocessing import Process import os,sys s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('127.0.0.1',8080)) s.listen(5) def work(conn,addr): print('===>进程号:%s'%os.getpid()) while True: try: data=conn.recv(1024) if not data:continue conn.send(data.upper()) except Exception as e: break conn.close() if __name__=='__main__': while True: conn,addr=s.accept() p=Process(target=work,args=(conn,addr)) p.start() s.close
客户端:
# -*- coding: utf-8 -*- __author__ = 'ShengLeQi' from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) while True: msg=input('>>').strip() if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) c.close()
四、Process对象的其他属性与join方法
属性介绍:
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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
例子:
from multiprocessing import Process import os,sys,time def work(name): print('task is running >%s' %name) time.sleep(1) if __name__=='__main__': # Process(target=work,args=('sheng',), kwargs={'name':'sheng'}) p1=Process(target=work, args=('sheng',)) #在windows 下Process一定要写在__main__下面 p1.start() p1.terminate() #终止p1进程 time.sleep(1) print(p1.is_alive()) #查看p1进程是否存在 print(p1.name) #Process-1 查看进程名字 print(p1.pid) print('主进程',os.getpid())
join函数等待主进程结束后在执行主函数。
from multiprocessing import Process import os,sys,time def work(name): print('task is running >%s' %name) time.sleep(1) if __name__=='__main__': # Process(target=work,args=('sheng',), kwargs={'name':'sheng'}) p1=Process(target=work, args=('sheng',)) #在windows 下Process一定要写在__main__下面 p2= Process(target=work, args=('lele',)) p1.start() p2.start() p1.join() #等待子进程执行完,在执行主进程 p2.join() print('主进程')
五、守护进程
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
例子1:
from multiprocessing import Process import os,sys,time def work(name): print('task is running >%s' %name) time.sleep(10) if __name__=='__main__': # Process(target=work,args=('sheng',), kwargs={'name':'sheng'}) p1=Process(target=work, args=('sheng',)) #在windows 下Process一定要写在__main__下面 p1.daemon =True #p1.start开始之前设置daemon, p1.start() time.sleep(0.5) print('主进程',os.getpid()) # 特点: #1、子进程在主进程结束后结束子进程 #2、子进程下面不能产生子进程
例子2:
#主进程代码运行完毕,守护进程就会结束 from multiprocessing import Process from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__=='__main__': p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止
六、同步锁
例子1:
import os,time from multiprocessing import Process,Lock def work(name,mutex): mutex.acquire() #锁开始 print('%s task is running %d' %(name,os.getpid())) time.sleep(2) print('%s=> is done %d'%(name,os.getpid())) mutex.release() #锁结束 if __name__=='__main__': mutex = Lock() p1=Process(target=work,args=('sheng',mutex)) p2=Process(target=work,args=('leqi',mutex)) p1.start() p2.start() print('===>主进程')
例子2:
import json import os import time from multiprocessing import Process,Lock def search(): dic=json.load(open('db.txt')) print('\033[32m[%s] 看到剩余票数<%s>\033[0m' %(os.getpid(),dic['count'])) def get_ticket(): dic = json.load(open('db.txt')) time.sleep(0.5) #模拟读数据库的网络延迟 if dic['count'] > 0: dic['count']-=1 time.sleep(0.5) # 模拟写数据库的网络延迟 json.dump(dic,open('db.txt','w')) print('\033[31m%s 购票成功\033[0m' %os.getpid()) def task(mutex): search() mutex.acquire() get_ticket() mutex.release() if __name__ == '__main__': mutex=Lock() for i in range(3): p=Process(target=task,args=(mutex,)) p.start()
七、进程之间通信
1、共享内存:
启动进程来减同一个内存数据:
from multiprocessing import Process,Manager,Lock def work(dic): dic['count']-=1 if __name__=='__main__': m=Manager() dic=m.dict({'count':100}) #共享字典dic, print(dic) p_l=[] for i in range(100): p=Process(target=work,args=(dic,)) p_l.append(p) p.start() for p in p_l: p.join() print(dic) # 结果: # {'count': 100} # {'count': 2} # 子进程同时修改共享字典dic,
使用加锁的机制:控制资源的抢占保证数据的正确性。
from multiprocessing import Process,Manager,Lock def work(dic,mutex): mutex.acquire() dic['count']-=1 mutex.release() if __name__=='__main__': mutex=Lock() m=Manager() dic=m.dict({'count':100}) #共享字典dic, print(dic) p_l=[] for i in range(100): p=Process(target=work,args=(dic,mutex)) p_l.append(p) p.start() for p in p_l: p.join() print(dic) # 结果 # {'count': 100} # {'count': 0} # 自己处理加锁的方式。 #缺点降低了运行的效率、
2、队列
from multiprocessing import Queue q=Queue(4) q.put('fist') q.put('scond') q.put('third') #put放入队列 q.put('fourth',block=True) #队列满了就抛异常(不卡) print(q.get())#get取出队列 print(q.get()) print(q.get())
八、生产者消费者模型
1、队列的应用
from multiprocessing import Process,Lock,Queue import time,os def producer(q): '''生产者程序''' for i in range(10): res='包子 %s' %i q.put(res) time.sleep(0.7) print('< %s生产了[%s]>' %(os.getpid(),res)) def consumer(q): '''消费者程序''' while True: res=q.get() time.sleep(1) print('< %s消费了[%s]>' %(os.getpid(),res)) if __name__=='__main__': q=Queue() p1=Process(target=producer,args=(q,)) c1=Process(target=consumer,args=(q,)) # 启动生产者 p1.start() # 启动消费者 c1.start() print('==>main')
上个例子中有问题:
生产者p1循环完之后,已经结束生产者进程,消费者c1一直等待就会卡住。消费者无法得知生产者什么时候完成。
解决方法:
from multiprocessing import Process, JoinableQueue import time, os def producer(q, name): for i in range(3): time.sleep(2) res = '%s%s' % (name, i) q.put(res) print('\033[45m<%s> 生产了 [%s]\033[0m' % (os.getpid(), res)) q.join() #生产者等待q的结束之后在结束(q的结束就是q为空的时候) def consumer(q): while True: res = q.get() # time.sleep(1.5) print('\033[34m<%s> 吃了 [%s]\033[0m' % (os.getpid(), res)) q.task_done() if __name__ == '__main__': q = JoinableQueue() # 生产者们:即厨师们 p1 = Process(target=producer, args=(q, '包子')) p2 = Process(target=producer, args=(q, '饺子')) p3 = Process(target=producer, args=(q, '馄饨')) # 消费者们:即吃货们 c1 = Process(target=consumer, args=(q,)) c2 = Process(target=consumer, args=(q,)) c1.daemon=True c2.daemon=True #把消费者设置为守护进程当主进程结束之后同时结束 p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() #主进程等待p1的结束之后继续执行后面 print('主')
九、进程池
1、进程池的使用:
import os,time from multiprocessing import Pool def work(n): print('task is running >%s' %os.getpid()) time.sleep(1) return n**2 if __name__=='__main__': p=Pool() # for i in range(10): # res=p.apply(work,args=(i,)) #同步执行,等待任务执行完毕 # print(res) res_l = [] for i in range(10): res=p.apply_async(work,args=(i,)) #异步执行 res_l.append(res) p.close() #不允许往进程池提交新的任务 p.join() #等待进程池的任务全部完成 for res in res_l: print(res.get()) #取出进程的返回值
2、进程池控制并发的套接字通信
服务端:
# -*- coding: utf-8 -*- __author__ = 'ShengLeQi' from socket import * from multiprocessing import Pool import os,sys s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('127.0.0.1',8080)) s.listen(5) def work(conn,addr): print('===>进程号:%s'%os.getpid()) while True: try: data=conn.recv(1024) if not data:continue conn.send(data.upper()) except Exception as e: break conn.close() if __name__=='__main__': p=Pool(3) while True: conn,addr=s.accept() p.apply_async(work,args=(conn,addr)) s.close
客户端:
# -*- coding: utf-8 -*- __author__ = 'ShengLeQi' from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) while True: msg=input('>>').strip() if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) c.close()
十、进程池之回调函数
回调函数的应用的例子:
当没有使用回调函数:如下
import requests,os,time from multiprocessing import Pool def get_page(url): #get网页内容的方法 print('<%s> get :%s' %(os.getpid(),url)) request=requests.get(url) if request.status_code==200: return {'url':url,'text':request.text} #返回一个字典 def parse_page(dic): print('<%s> parse :%s' %(os.getpid(),res)) print('url:%s size :%s' %(dic['url'],len(dic['text']))) if __name__=='__main__': p = Pool(4) urls=[ 'https://www.baidu.com', 'https://www.python.org', 'http://www.sina.com.cn', 'https://www.openstack.org', 'https://about.gitlab.com' ] res_l=[] for url in urls: res=p.apply_async(get_page,args=(url,)) #apply_async使用异步启动进程(get_page) res_l.append(res) p.close() #先close()保证没有新的进程添加到进程池 p.join() #等待钱需要close() for res in res_l: parse_page(res.get())
上面的例子中有问题: =》
效率问题:需要等待进程池里面的函数都执行完毕,才能继续执行主进程(当进程池所有的进程把网页下载完成后才能分析提取页面内容。)
使用回调函数来提高程序的效率。当进程池的一个进程把url的网页下载下来之后就开始分析提取此网页内容。
import requests,os,time from multiprocessing import Pool def get_page(url): #get页面函数 print('<%s> get :%s' %(os.getpid(),url)) request=requests.get(url) if request.status_code==200: return {'url':url,'text':request.text} def parse_page(dic): #分析页面内容函 print('<%s> parse: %s' %(os.getpid(),dic['url'])) print('url:%s size :%s' %(dic['url'],len(dic['text']))) if __name__=='__main__': p = Pool(4) #数值4是意味可以并发4个进程,不写默认是系统cpu内核数 urls=[ 'https://www.baidu.com', 'https://www.python.org', 'http://www.sina.com.cn', 'https://www.openstack.org', 'https://about.gitlab.com' ] res_l=[] for url in urls: p.apply_async(get_page,args=(url,),callback=parse_page) #通知主进程回调parse_page函数 使用回调函数 p.close() p.join()
运行结果:
# <30700> get :https://www.baidu.com
# <26740> get :https://www.python.org
# <10516> get :http://www.sina.com.cn
# <10516> get :https://www.openstack.org
# <29920> parse: http://www.sina.com.cn
# url:http://www.sina.com.cn size :600911
# <29780> get :https://about.gitlab.com
# <29920> parse: https://www.baidu.com
# url:https://www.baidu.com size :2443
# <29920> parse: https://www.python.org
# url:https://www.python.org size :48708
# <29920> parse: https://about.gitlab.com
# url:https://about.gitlab.com size :81129
# <29920> parse: https://www.openstack.org
# url:https://www.openstack.org size :60365

浙公网安备 33010602011771号