1、进程的其他方法
getpid,pid,ppid
import os from multiprocessing import Process def f1(): print('子进程的pid:',os.getpid()) print('子进程的父进程的pid:',os.getpid()) print('gggg') def f2(): print('ffff') if __name__ == '__main__': p1 = Process(target=f1,name='zhouyou') p2 = Process(target=f2,) p1.start() p2.start() print(p1.name) print('子进程的pid:',p1.pid) print('父进程的pid:',os.getpid())
is_alive :判断子进程是否还活着,是否还在运行
terminate:给操作系统发送一个结束进程的信号
import time from multiprocessing import Process def f1(): time.sleep(3) print('子进程1') if __name__ == '__main__': p = Process(target=f1,) p.start() print(p.is_alive()) # 判断子进程是否还活着,是否还在运行 p.terminate() # 给操作系统发送一个结束进程的信号 time.sleep(0.5) print(p.is_alive())
二:孤儿进程和僵尸进程
参考博客:http://www.cnblogs.com/Anker/p/3271773.html
一:僵尸进程(有害)
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
二:孤儿进程(无害)
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
三:僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
3、守护进程
之前我们讲的子进程是不会随着主进程的结束而结束,子进程去拿不执行完之后,程序才结束。守护进程就可以让主进程结束了之后,子进程立马结束
主进程创建守护进程:
其一、守护进程会在主进程代码结束执行结束就终止
其二、守护进程内无法再开启子进程,否则抛出异常。
注:进程之间时互相独立的,主进程代码运行结束,守护进程随即终止
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process # 主进程一旦结束,子进程都结束了 def f1(): time.sleep(3) print('vvvv') def f2(): time.sleep(5) print('普通子进程结束') if __name__ == '__main__': p1 = Process(target=f1,) ''' 将该进程设置为守护进程,必须写在start之前,意思是如果我的主进程代码运行结束了,子进程不管运行到哪里,都直接结束 ''' p1.daemon = True p1.start() p2 = Process(target=f2,) p2.start() # 等到2号普通进程结束,才执行下面的主进程代码 # p2.join() # 守护进程会跟着父进程的代码运行结束,就结束 print('主进程结束')
4、进程锁(同步锁/异步锁)
多进程抢占输出资源,导致打印错乱的示例
import os import time import random from multiprocessing import Process def work(n): print(f"{n}:{os.getpid()} is running") time.sleep(random.random()) print(f"{n}:{os.getpid()} is running") if __name__ == '__main__': for i in range(6): p =Process(target=work,args=(i,)) p.start()
加锁:由并发改成了串行,牺牲了运行效率,但避免了竞争,保证了数据安全
由并发变成了串行,牺牲了运行效率,但避免了竞争
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os ,time from multiprocessing import Process,Lock def work(n,lock): # 加锁,保障每一次只有一个进程再执行所里面的程序,这一段程序对于所有协商这个锁的进程,大家都变成了串行 lock.acquire() print(f"{n}:{os.getpid()} is running") time.sleep(1) print(f"{n}:{os.getpid()} is running") # 解锁,解锁之后其他进程才能去执行自己的程序 lock.release() if __name__ == '__main__': lock = Lock() for i in range(6): p = Process(target=work,args=(i,lock)) p.start()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。 IPC通信机制(了解):IPC是intent-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。IPC不是某个系统所独有的,任何一个操作系统都需要有相应的IPC机制, 比如Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信,而Linux上可以通过命名共享内容、信号量等来进行进程间通信。Android它也有自己的进程间通信方式,Android建构在Linux基础上,继承了一 部分Linux的通信方式。
模拟火车票抢票
1、不加锁抢票
ticket.db 文件
{'count': 0}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,Lock import time,random # 查看剩余票数 def search(i): with open('ticket.db','r',encoding='utf-8') as f: ticket_data = f.read() t_data = eval(ticket_data) print('\033[43m%s号查询剩余票数%s\033[0m'%(i,t_data['count'])) def get(i): with open('ticket.db','r',encoding='utf-8') as f: ticket_data = f.read() t_data = eval(ticket_data) time.sleep(0.1) if t_data['count']>0: t_data['count'] -= 1 print('\033[42m%s号购票成功\033[0m'%i) time.sleep(0.2) with open('ticket.db','w') as f: f.write(str(t_data)) else: print('\033[41m没票了!!!!\33[0m') if __name__ == '__main__': for i in range(10): p1=Process(target=search,args=(i,)) p1.start() for i in range(10): p2 = Process(target=get,args=(i,)) p2.start()
2、加锁抢票
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,Lock import time,random # 查看剩余票数 def search(i): with open('ticket.db','r',encoding='utf-8') as f: ticket_data = f.read() t_data = eval(ticket_data) print('\033[43m%s号查询剩余票数%s\033[0m'%(i,t_data['count'])) def get(i,ll): ll.acquire() with open('ticket.db','r',encoding='utf-8') as f: ticket_data = f.read() t_data = eval(ticket_data) time.sleep(0.1) if t_data['count']>0: t_data['count'] -= 1 print('\033[42m%s号购票成功\033[0m'%i) time.sleep(0.2) with open('ticket.db','w') as f: f.write(str(t_data)) else: print('\033[41m没票了!!!!\33[0m') ll.release() if __name__ == '__main__': ll = Lock() for i in range(10): p1=Process(target=search,args=(i,)) p1.start() for i in range(10): p2 = Process(target=get,args=(i,ll)) p2.start()
5、数据共享
进程间数据是独立的,可以借助队列或者管道实现通信,二者都是基于消息传递的,虽然进程间数据独立,但可以通过Manager实现数据共享,
进程间尽量避免通信,及时需要通信,也应该选择安全的工具来避免加锁带来的问题,应该尽量避免使用本届所讲的共享数据的方式,以后会尝试使用数据库来解决进程间的数据共享问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process,Manager,Lock # a = 10 # tmp = a # tmp -= 1 # a = tmp # a -= 1 # a= a-1 def f1(m_d,l2): with l2: # l2.acquire tmp = m_d['num'] tmp -= 1 time.sleep(0.1) m_d['num'] = tmp # l2.release() if __name__ == '__main__': m = Manager() l2 = Lock() m_d = m.dict({'num':100}) p_list = [] for i in range(10): p = Process(target=f1,args=(m_d,l2)) p.start() p_list.append(p) [pp.join() for pp in p_list] print(m_d['num'])
6、for 循环
import time from multiprocessing import Process def f1(): time.sleep(0.2) print('vvv') if __name__ == '__main__': p_list = [] # for 循环创建子进程,并且完成主进程等待所有紫禁城执行结束,才继续进行 for i in range(10): p = Process(target=f1,) p.start() p_list.append(p) p.join() print('主进程结束')
5、队列
进程之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道。这两种方式都是使用消息传递的,队列就像是一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出,先进先出
q = Queue(n) #创建一个队列对象,队列长度为3,先进先出 q.get() q.get_nowait() # 不阻塞程序,但是会报错queue.Empty,可以通过捕获气场来进行其他操作 q.put() q.put_nowait() # 不阻塞程序,但是会报错queue.Full,可以通过捕获气场来进行其他操作 q.qsize() # 返回当前队列的内容长度 q.empty() # 空了返回True,没空返回False q.full() # 满了返回True,没满返回False
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,Queue q = Queue(3) q.put(1) print('>>>>',q.qsize()) # 返回当前队列的内容长度 print(q.full()) q.put(2) q.put(3) q.put(4) # 放入数据的似乎,如果队列满了,程序会在put操作的地方阻塞 try: q.put_nowait(4) # 不阻塞程序,但是会报错queue.Full,可以通过捕获异常来进行其他操作 except: print('队列满了,请绕行') print('ssss') print(q.get()) print(q.get()) print(q.get()) print('是不是空了:',q.empty()) q.put(4) print('是不是空了啊:',q.empty()) # True或者False,因为q再put数据的时候,有一个细微的延迟 print(q.get()) # 如果队列空了,程序会在这个地方卡住,也就是阻塞程序 try: q.get_nowait() # queue.Empty except: print('队列空了') print('拿多了')
queue的其他方法
q.close() # 关闭队列,防止队列中加入更多的数据,调用此方法时,后台进程将继续写入那些已入队列但尚未写入的数据,将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法,关闭队列不会在队列使用者中生成任何类型的数据结束信号或者异常。例如,某个使用者正被阻塞在get()操作上,关闭生产者的队列不会导致get()方法返回错误。 q.cancel_join_thread() # 不会在进程推出时自动连接后台线程,这可以防止join_thread方法阻塞 q.join_thread() # 连接队列的后台线程,此方法用于在调用q.close()方法后,等待所有队列想别小号。默认情况下。此方法不hiq的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为
2、基于队列的进程通信
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 按照编号看注释 import time from multiprocessing import Process,Queue # 8、q = Queue(2) 创建一个Queue对象,如果写在这里,那么在windows下,子进程去执行的时候,我们直达票子进程中还会执行这个代码,但是子进程中不能够再次创建了,也就是这个q就是你主进程中创建的那个q,通过我们下面再主进程中先添加一个字符串后,再去开启子进程,你会发现,小西瓜这个字符串还在队列总,也就是说,我们使用的哈市主进程中创建的这个队列。 def f(q): # q = Queue() # 9、我们在主进程中开启了一个q,如果我们在子进程中的函数里面再开一个q,那么下面q.put(''苹果多少钱)添加到了新创建的这个q里面了 q.put('苹果多少钱') # 4、调用主函数中p进程传递过来的进程参数,put函数为相对列中添加一条数据 # print(q.qsize()) # 6、查看队列中由多少条数据了 def f2(q): print('>>>>>>>') print(q.get()) # 5、取数据 if __name__ == '__main__': q = Queue() # 1、创建一个Queue对象 q.put('小西瓜') p = Process(target=f,args=(q,)) #2、 创建进程 p2 = Process(target=f2,args=(q,)) # 3、创建一个进程 p.start() p2.start() time.sleep(1) # 7、如果阻塞一点时间,就会出现主进程运行太快,导致我们在子进程中产看qsize为1个 print(q.get()) p.join()
3、通过队列来实现一个生产者消费者模型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process,Queue # s生产者 def producer(q): for i in range(10): time.sleep(0.5) s = '大包子%s号'%i print(s+'新鲜出炉,尽情享用') q.put(s) def consumer(q): while 1: time.sleep(1) baozi = q.get() print(baozi+'被吃了') if __name__ == '__main__': q = Queue(10) pro_p = Process(target=producer,args=(q,)) con_p = Process(target=consumer,args=(q,)) pro_p.start() con_p.start()
(2)改进版
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process,Queue # 生产者 def producer(q): for i in range(10): time.sleep(0.7) s = '大包子%s号'%i print(s+'新鲜出炉,尽情享用') def consumer(q): while 1: time.sleep(0.2) try: baozi = q.get_nowait() # 不合适,因为无法确定做的快,还是吃的快,按照这样的写法,如果吃的快,那么这个消费者的车需就会直接结束,不能满足要求 except Exception: break print(baozi+'被吃了') if __name__ == '__main__': q = Queue(10) pro_p = Process(target=producer,args=(q,)) con_p = Process(target=consumer,args=(q,)) pro_p.start() con_p.start()
再次改进,子进程生产者在生产完毕后发送结束信号None
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process,Queue # 生产者 def producer(q): for i in range(10): time.sleep(0.2) s = '大包子%s'%i print(s+'新鲜出炉,尽情享用吧') q.put(s) q.put(None) # 发送一个结束任务信号,来终端消费者的程序 def consumer(q): while 1: time.sleep(0.5) baozi = q.get() if baozi == None: print('都吃完了,该回家了') break print(baozi+'被吃了') if __name__ == '__main__': q = Queue(30) pro_p = Process(target=producer,args=(q,)) con_p = Process(target=consumer,args=(q,)) pro_p.start() con_p.start()
JoinableQueue()
这就像一个queu对象,但队列荀彧项目的使用者通知生辰这像目已经被成功处理了。通知进程是使用共享的信号和条件变量来实现的。
JoinableQueue的实例p除了Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,知道队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法位置,也就是队列中的数据全部被get拿走了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from multiprocessing import Process,Queue,JoinableQueue #生产者 def producer(q): for i in range(10): time.sleep(0.2) s = '大包子%s号'%i print(s+'新鲜出炉,尽情享用') q.put(s) q.join() # 就等着task_done()信号的熟练,和put进去的梳理相同时,才继续执行 print('所有的任务都被处理了,继续潜行吧') def consumer(q): while 1: time.sleep(0.5) baozi = q.get() print(baozi+'被吃了') q.task_done() # 给队列发送一个去除的这个任务已经处理完毕的信号 if __name__ == '__main__': # q = Queue(30) q = JoinableQueue(30) # 同样是一个长度为30的队列 pro_p = Process(target=producer,args=(q,)) con_p = Process(target=consumer,args=(q,)) pro_p.start() con_p.daemon = True con_p.start() pro_p.join() print('主进程结束')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from multiprocessing import Process,JoinableQueue import time,os,random def consumer(q): while 1: res = q.get() # time.sleep(random.randint(1,3)) time.sleep(random.random()) print('\033[43m%s 吃 %s\033[0m'%(os.getpid(),res)) q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走并执行完了 def producer(name,q): for i in range(10): time.sleep(random.random()) res='%s%s'%(name,i) q.put(res) print('\033[46m%s 吃 %s\033[0m'%(os.getpid(),res)) print('%s生产结束'%name) q.join() # 生产完毕,使用此方法进行阻塞,知道队列中所有项目均被处理 print('%s生产结束。。。。'%name) 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 # 如果不加守护,那么主进程结束不了,生产者进程的结束表示者生产的所有人任务都已经被处理完了 # 开始 p_1 = [p1,p2,p3,c1,c2] for p in p_1: p.start() p1.join() # 要确保你的生产者进程结束了,生产者进程的结束标志着你生产的所有的人任务都已经被处理完了 p2.join() p3.join() print('主进程')