python中的进程操作
运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。根据之前所学的知识,并不能实现创建进程,所以必须借助python中强大的模块multiprocess.
进程的唯一标识 ---> id:
查看进程号:
在终端查看: tasklist
在本文件查看: print(os.getpid()) 子进程号,是随时变动的
查看父进程号: print(os.getppid()) 父进程号,依赖于一个软件,是不变的
查看同一个软件下的进程: 终端窗口输入 tasklist|findstr python
multiprocessing模块
严格来说: multiprocess不是一个模块而是python中一个操作、管理进程的包。在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块特别多,所以能大致分为四个部分: 进程的创建, 进程的同步, 进程池, 进程之间的数据共享.
进程的创建: multiprocessing.process模块
process模块是一个创建进程的模块,我们借助这个模块,就可以完成进程的创建
参数:
参数强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
主要参数:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'egon',)
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5 name为子进程的名称,name='自定义进程名'
方法介绍:
p.start(): 启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,
使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
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字符的字符串。
这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
python中process模块的注意事项:
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用 if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
使用process模块创建进程
01.在一个python进程中开启一个子进程
import time from multiprocessing import Process def func(name): print('hello',name) if __name__ == '__main__': p = Process(target=func, args=('子进程',))或 p = Process(target=func,kwargs={'a':'子进程'}) p.start() time.sleep(1) print('执行主进程')
02. 多个进程同时运行(子进程的执行顺序并不受启动顺序的影响)
import time from multiprocessing import Process def f(name): print('hello', name) if __name__ == '__main__': for i in range(5): p = Process(target=f, args=('bob',)) p.start()
特殊的join方法:
01.只有一个子进程的情况下的join
import time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) print('我是子进程') if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() p.join() print('我是父进程')
02. 多个子进程同时运行: for循环+join 模拟并发
from multiprocessing import Process import time def task(n): print('%s running'% n) time.sleep(3) print('%s ending'% n) def main(): print('主程序运行...') if __name__ == '__main__': p_lst = [] for i in range(1,4): p = Process(target=task,args=('%s'% i,)) p.start() p_lst.append(p) for p in p_lst: p.join() main()
03.用join还会出现另外一种情况,就会降低执行效率
from multiprocessing import Process import time def task(n): print('%s running'% n) time.sleep(3) print('%s ending'% n) def main(): print('主程序运行...') if __name__ == '__main__': for i in range(1,4): p = Process(target=task,args=('%s'% i ,)) p.start() p.join() main()
继承Process类的形式开启进程
from multiprocessing import Process class MyProcess(Process): def __init__(self,n): super().__init__() self.n = n def run(self): # 固定格式 ''' 子进程的代码''' print('%s is running' % self.n) if __name__ == '__main__': p = MyProcess('小一') p.start() print('主进程......')
进程之间的数据隔离
from multiprocessing import Process import time x = 1000 def task(): time.sleep(3) global x x = 2 print('子进程---> ', x) # 此时的x = 2 if __name__ == '__main__': p = Process(target=task,) p.start()
# 向操作系统发起一个请求 time.sleep(5) print('主进程---> ',x) # 这儿的x = 1000
守护进程
守护进程:
会随着主进程的结束而结束(进程之间是相互独立的). 主进程创建守护进程,首先,守护进程会在主进程代码执行结束后就终止. 其次,守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
守护进程的启动:
from multiprocessing import Process import time import os class Myprocess(Process): def __init__(self,person): super().__init__() self.person = person def run(self): print(os.getpid(),self.name) print('%s正在和女主播聊天' %self.person) p = Myprocess('小胖子') p.daemon=True # 必须在start之前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p就结束 p.start() time.sleep(5) print('主进程...')
主进程代码执行结束后守护进程立即结束
from multiprocessing import Process 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() time.sleep(0.1) print("main-------")#打印这一行是主进程代码结束,则守护进程p1应该被终止. #可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.
socket聊天并发实例
使用多进程实现socket并发的server
from multiprocessing import Process import socket sk = socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 端口重置 sk.bind(('本地回环地址',端口)) sk.listen(5) def talk(conn, client_addr): while 1: try: msg = conn.recv(1024) if not msg: break conn.send(msg.upper()) except Exception: break if __name__ =='__main__': while 1: conn,addr = sk.accept() p = Process(target=talk, args=(conn,client_addr)) p.start()
import socket client = socket.socket() client.connect(('服务端ip',端口)) while 1: msg = input('请输入>>>').strip() if not msg: break client.send(msg.encode('utf-8')) msg1 = client.recv(1024) print(msg1.decode('utf-8'))
进程同步(multiprocess.Lock)
锁: --> multiprocess.Lock
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题
from multiprocessing import Process import os import time def work(n): print('%s: %s is running' %(n, os.getpid())) time.sleep(1) print('%s:%s is done' %(n, os.getpid())) if __name__ == '__main__': for i in range(3): p=Process(target=work,args=(i,)) p.start()
from multiprocessing import Process,Lock import os import time def work(lock,n): lock.acquire() print('%s: %s is running' % (n, os.getpid())) time.sleep(random.random()) print('%s: %s is done' % (n, os.getpid())) lock.release() if __name__ == '__main__': lock=Lock() for i in range(5): p=Process(target=work,args=(lock,i)) p.start() # 由并发变成了串行,避免了不同进程之间对资源的竞争,但一定程度上牺牲了运行效率
在生活中,我们每个人都对应的要处理一些数据,比如春运抢票,热映电影票等等,那这个时候使用进程锁就很有必要了,就以简单的模拟春运抢票为例,来看看数据安全的重要性.
from multiprocessing import process import json import time # 模拟数据写入 dic = {"count":300} with open('db','w',encoding='utf-8')as f1: json.dump(dic,f1) def search(): with open('db','r',encoding='utf-8')as f2: search_dic = json.load(f2) print('剩余票数:%s张'%dic["count"]) def get(): with open('db','r',encoding='utf-8')as f: get_dic = json.load(f3) if get_dic["count"]>0: get_dic["count"] -= 1 time.sleep(0.5) # 模拟网络延迟 with open('db','w',encoding='utf-8')as f3: json.dump(get_dic,f3) print('购票成功') def task(): search() get() if __name__ =="__main__": for i in range(100): p = Process(target=task) p.start()
from multiprocessing import Process,Lock import time import json # 用上一段代码中的db文件 def search(): with open('db','r',encoding='utf-8')as f1: dic=json.load(f1) print('剩余票数%s' %dic['count']) def get(): with open('db','r',encoding='utf-8')as f2: dic=json.load(f2) time.sleep(0.5) #模拟读数据的网络延迟 if dic['count'] >0: dic['count']-=1 time.sleep(0.5) #模拟写数据的网络延迟 with open('db','w',encoding='utf-8')as f3: json.dump(dic,f3) print('购票成功') else: print('购票失败') def task(lock): search() lock.acquire() get() lock.release() if __name__ == '__main__': lock = Lock() for i in range(100): #模拟并发100个客户端抢票 p=Process(target=task,args=(lock,)) p.start()
给数据上了锁之后,同一时间只会有一个任务进行,保护了数据安全,但牺牲了速度
进程间通信 --- 队列(Queue)
IPC通讯机制(Inter-Process Communication)
队列Queue:
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递.Queue([maxsize])时队列中允许的最大项数,如果省略了这个参数就没有大小限制.底层队列使用该管道和锁实现,此外还需要运行线程以便队列中的数据传输到底层管道中.
q = Queue(),q具有以下方法:
q.get( [ block [ ,timeout ] ] )
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。
block用于控制阻塞行为,默认为True. 如果设置为False,会引发Queue.Empty异常(定义在Queue模块中).
timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
q.get_nowait( )
非阻塞,如果拿不到数据就会执行另一个进程,数据不会丢失
q.put(item [, block [,timeout ] ] )
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。在某种程度上会造成数据的丢失
block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。
timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
q.qsize()
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。
q.empty()
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
q.full()
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。
q.close()
关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
q.cancel_join_thread()
不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。
q.join_thread()
连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
上实例:
from multiprocessing import Queue q = Queue(3) q.put(1) q.put(1) q.put(1) q.put(1) # 可以看见上面的队列只能容纳3个,所以到这儿会发生阻塞,等待队列中有空余的位置,再进行添加 try: q.put_nowait(3) except: print('队列已满') # 可以用try语句来处理阻塞 print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 和上面put方法一样,队列已经空了,会出现阻塞 try: q.get_nowait() except: print('队列已空') # 用try语句可以处理阻塞
多个进程之间的数据是相互隔离的,但通过队列可以实现相互通信
from multiprocessing import Process,Queue def pro(q): q.put(123) def con(q): print(q.get()) if __name__ == '__main__': q = Queue(3) # 创建一个Queue对象 p = Process(target=pro,args=(q,)) # 开启p进程,执行pro p.start() p1 = Process(target=con,args=(q,)) # 开启p1进程,执行con p1.start()
队列之间的双端通信
from multiprocessing import Process,Queue import time def pro(p): q.put(123) time.sleep(0.1) # 模拟网络延迟 print('in pro ---> ',q.get()) def con(p): print('in con ---> ',q.get()) q.put('aaa') if __name__ == '__main__': q = Queue(3) p = Process(target=pro,args=(q,)) p.start() p1 = Process(target=con,args=(q,)) p1.start()
生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
在并发编程中生产者和消费者模型能够解决大多数的并发问题,通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度,在线程世界中,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
基于队列实现的生产者消费者模型
from multiprocessing import Process,Queue def pro(q): for i in range(10): q.put('包子%s' % i) print('生产了包子%s' % i) def con(q): while 1: task = q.get() print('吃了%s' % task) if __name__ == '__main__': q = Queue() p = Process(target=pro, args=(q,)) c = Process(target=con, args=(q,)) p.start() c.start()
这时的主进程一直不会结束,是因为生产者pro在生产完就结束了,但消费者con还一直卡在q.get()这个节点,针对这个问题,我们可以让生产者在生产结束后发送一个信号,告诉消费者生产完成了,这样消费者就可以跳出死循环了
from multiprocessing import Process, Queue import time def consumer(q, name): # 消费者 while True: task = q.get() if task is None: break time.sleep(1) print('%s吃了%s'%(name, task)) def producer(q, n): for i in range(n): # 设定生产者的人数 time.sleep(1) print('生产了巧克力%s'%i) q.put('巧克力%s'%i) if __name__ == '__main__': q = Queue() pro_lst = [] for i in range(5):# 设定5个生产者的进程对象 p = Process(targe=producer, args=(q,10)) # args第二个参数是巧克力编号 p.start() pro_lst.append(p) # 添加生产者进程对象 # 消费者: p1 = Process(target=consumer, args=(q, '小胖')) p2 = Process(target=consumer, args=(q,'大胖')) p1.start() p2.start() for p in pro_lst: p.join() q.put(None) q.put(None) # 有几个消费者进程对象就发送几次None
进程之间的数据共享
进程之间应该尽量避免通信,即便是需要通信,也要选择进程安全的工具来避免加锁带来的问题
Manager模块
manager模块中关联了很多的数据类型,但并不是所有的数据类型都是来做数据共享的,只是顺带着包含了能够处理数据共享问题的数据类型,List,Dict在manager中是可以共享的数据类型
append/extend/pop/remove/insert 基本上数据都是安全的,不需要进行加锁
而对list和dict中的元素进行 +=, -=,*=,/=都是不安全的,都需要进行加锁
from multiprocessing import Manager,Process,Lock def func(dic,lock): with lock: # 如果不加锁,就会出现数据错乱的情况 dic['count'] -= 1 if __name__ == '__main__': m = Manager() lock = Lock() dic = m.dict({'count':100}) p_lst = [] for i in range(100): p = Process(target=func,args=(dic,lock)) p.start() p_lst.append(p) for p in p_lst: p.join() print(dic)
进程池,multiprocess.Pool模块
进程池:
在程序实际执行处理问题的过程中,忙时可能会有成千上万的任务需要执行,闲时只有零零散散的任务,那在成千上万个任务需要执行时,必须要创建成千上万个进程吗?那效率得多慢。首先,创建进程需要消耗时间,销毁进程也需要消耗时间。其次即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。所以我们就可以通过进程池来执行,创建一个进程池,里面有固定数量的进程,有需求进来就拿进程池中的一个进程来处理任务,等这个任务处理完,把进程再放回到进程池中等待任务。进程池中进程的数量是固定的,同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
multiprocess.Pool模块
Pool([numprocess [,initializer [,initargs]]] ):创建进程池
1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值,一般是CPU个数的1-2倍 2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None 3 initargs:是要传给initializer的参数组
方法:
关闭进程池: p.close()
p.join(), 等待所有的工作进程退出,这个方法只能在close()或teminate()之后进行调用
p.terminate(), 立即终止所有工作进程,且不执行任何清理或者结束任何挂起的工作
同步提交: p.apply(func,(,))
apply操作并不会在所有池工作进程中且执行func函数,如果要通过不同参数并发执行func函数,就需要在不同的线程中调用p.apply()或者使用p.apply_async()
进程池的异步提交: p.apply_async(func,(,))
from multiprocessing import Pool def func(i): return i * i # 异步提交并获取进程的返回值 if __name__ == '__main__': p = Pool() ret_l = [] for i in range(30): ret = p.apply_async(func,(i,)) ret_l.append(ret) for ret in ret_l: print(ret.get())
map方法:
map方法是一种简单的apply_async方法,内置了close和join功能
from multiprocessing import Pool def func(i,): return i * i if __name__ == '__main__': p = Pool() ret_l = p.map(func,range(30)) # map方法的第二个参数必须是可迭代对象 for ret in ret_l: print(ret)
进程池版本的socket并发聊天
import os import socket from multiprocessing import Pool server = socket.socket() server.bind(('127.0.0.1',8000)) server.listen(5) def talk(conn): while 1: try: msg = conn.recv(1024) if not msg: break conn.send(msg.upper()) except Exception: break if __name__ == '__main__': p = Pool(4) while True: conn,addr = server.accept() p.apply_async(talk,args=(conn,))
import socket client = socket.socket() client.connect(('127.0.0.1',8000)) while 1: msg = input('>>>').strip() if not msg: break client.send(msg.encode('utf-8')) msg = client.recv(1024) print(msg.decode('utf-8'))
callback,回调函数:
需要回调函数的场景: 进程池中任何一个任务一旦处理完了,就立即通知主进程,主进程则调用一个函数去处理这个结果,调用的这个函数就是回调函数.我们可以把耗时的任务放在进程中,然后指定回调函数,这样主进程在执行回调函数时就省去了I/O的过程,直接拿到任务的结果
from multiprocessing import Pool from collections import request def parser_page(content): print(len(content)) def get_page(url): ret = request.urlopen('url地址') content = ret.read().decode('utf-8') return content if __name__ == '__main__': url_lst = [ 'http://www.baidu.com', 'http://www.jd.com', 'http://www.sogou.com' ] p = Pool() for url in url_lst: ret = p.apply_async(get_page,(url,),callback=parser_page) p.close() p.join()