python并发编程
Python并发编程
-
multiprocessing模块介绍
-
Python中的多线程无法利用多核优势,如果想要充分的使用多核CPU的资源(os.cpu_count)查看,在Python中大部分情况需要使用多进程,Python提供了multiprocessing,它用来开启子进程,并在子进程中执行我们定制的任务,该模块的功能众多:支持子进程,通信和共享数据,执行不同形式的同步,提供了Process.Queue,Pipe,Lock等组件
需要强调的是,与线程不同的是,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内
-
-
Process类的介绍
- 创建进程的类
- Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
- 强调:
- 1.需要使用关键字参数打的方式来指定参数
- 2.args指定的为传给target函数的位置参数,是一个元祖的形式,必须有逗号
- 参数介绍
- group 参数未使用,始终为None
- target 表示未调用对象,即子进程要执行的任务
- args 表示调用对象的位置参数元祖,args = (1,2,'egon")
- kwargs表示调用对象的字典,kwargs =
- name为子进程的名称
- 方法介绍
- p.start() ; 启动进程,并调用该子进程中的p.run()
- p.run() ; 进程启动时运行的方法,正是他去调用target指定的函数,我们自定义类的类中一定要实现该方法
- p.terminate() : 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程会成为僵尸进程,使用该方法需要特别小心这种情况,如果p还保存了一个锁,name也将不会被释放,进而导致死锁
- p.is_alive([timeout]) : 如果p还在运行,返回True
- p.jion() ; 主线程等待p终止(强调:是主线程处于等的状态,为p是处于运行的状态) timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,不能join住run开启的进程
- 属性介绍
- p.damon ; 默认值时False,如果设为True,代表p为后台运行的守护进程,当父进程终止时,p也随之终止,并且设置为True后,p不能创建自己的新继承,必须在p.star()之前设置
- p.name: 进程的名称
- p.pid : 进程的pid
- p.exitcode ; 进程在运行时为None,如果为-N,表示被信号N结束(了解即可)
- p.authkey ; 进程的身份验证键,默认是由os.urandom()随机生成的字符串,这个键的用途是为涉及网络连接底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
- 创建进程的类
5.Process类的使用
-
开启子进程的两种方式
-
from multiprocessing import Process import time class MyProcess(Process): def __init__(self, name): super().__init__() self.name = name def run(self): # 必须定义一个run方法 print('%s is runing' % (self.name)) time.sleep(3) print('%s is done' % (self.name)) if __name__ == '__main__': p = MyProcess("小明") p.start() print('===主进程')
-
from multiprocessing import Process import time def task(name): print(f"{name} is running") time.sleep(3) print(f"{name} is done") if __name__ == "__main__": p = Process(target = task,args = ("小明",)) #p = Process(target = task,kwargs = {'name":"小明"}) p.start() print("===主进程")
-
-
验证进程之间的空间隔离
-
from multiprocessing import Process import time x = 1000 def task(): global x x = 2 if __name__ == "__main__": p = Process(target=task) p.start() time.sleep(3)#为了让子进程在主进程之前执行 print(x) #结果为1000,说明子进程未对主进程的x进行修改
-
-
进程对象的join方法
-
from multiprocessing import Process import time def task(n): time.sleep(3) print('%s is running' %n) if __name__ == '__main__': p_l = [] start_time = time.time() for i in range(1,4): p = Process(target=task,args=(i,)) p.start() p_l.append(p) # 对着三个自己成使用三个join for i in p_l: i.join() print(time.time() - start_time,'主进程开始运行....')
-
-
进程对象的其他属性(了解)
-
from multiprocessing import Process import time import os def task(n): time.sleep(3) print('%s is running' %n,os.getpid(),os.getppid()) if __name__ == '__main__': p1 = Process(target=task,args=(1,),name = '任务1') # print(p1.name) # 给子进程起名字 # for i in range(3): # p = Process(target=task, args=(1,)) # print(p.name) # 给子进程起名字 p1.start() # p1.terminate() # time.sleep(2) # 睡一会,他就将我的子进程杀死了。 # print(p1.is_alive()) # False print(p1.pid) # print('主') print(os.getpid())
-
-
僵尸进程和孤儿进程
-
参考博客:http://www.cnblogs.com/Anker/p/3271773.html 一:僵尸进程(有害) 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下 我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。 因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息: 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等) 2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 二:孤儿进程(无害) 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。 我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容 import os import sys import time pid = os.getpid() ppid = os.getppid() print 'im father', 'pid', pid, 'ppid', ppid pid = os.fork() #执行pid=os.fork()则会生成一个子进程 #返回值pid有两种值: # 如果返回的pid值为0,表示在子进程当中 # 如果返回的pid值>0,表示在父进程当中 if pid > 0: print 'father died..' sys.exit(0) # 保证主线程退出完毕 time.sleep(1) print 'im child', os.getpid(), os.getppid() 执行文件,输出结果: im father pid 32515 ppid 32015 father died.. im child 32516 1 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。 三:僵尸进程危害场景: 例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。 四:测试 #1、产生僵尸进程的程序test.py内容如下 #coding:utf-8 from multiprocessing import Process import time,os def run(): print('子',os.getpid()) if __name__ == '__main__': p=Process(target=run) p.start() print('主',os.getpid()) time.sleep(1000) #2、在unix或linux系统上执行 [root@vm172-31-0-19 ~]# python3 test.py & [1] 18652 [root@vm172-31-0-19 ~]# 主 18652 子 18653 [root@vm172-31-0-19 ~]# ps aux |grep Z USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 18653 0.0 0.0 0 0 pts/0 Z 20:02 0:00 [python3] <defunct> #出现僵尸进程 root 18656 0.0 0.0 112648 952 pts/0 S+ 20:02 0:00 grep --color=auto Z [root@vm172-31-0-19 ~]# top #执行top命令发现1zombie top - 20:03:42 up 31 min, 3 users, load average: 0.01, 0.06, 0.12 Tasks: 93 total, 2 running, 90 sleeping, 0 stopped, 1 zombie %Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 1016884 total, 97184 free, 70848 used, 848852 buff/cache KiB Swap: 0 total, 0 free, 0 used. 782540 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND root 20 0 29788 1256 988 S 0.3 0.1 0:01.50 elfin #3、 等待父进程正常结束后会调用wait/waitpid去回收僵尸进程 但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的 解决方法一:杀死父进程 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程 参考python2源码注释 class Process(object): def join(self, timeout=None): ''' Wait until child process terminates ''' assert self._parent_pid == os.getpid(), 'can only join a child process' assert self._popen is not None, 'can only join a started process' res = self._popen.wait(timeout) if res is not None: _current_process._children.discard(self) join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除 解决方法三:http://blog.csdn.net/u010571844/article/details/50419798
-
-
守护进程
-
主进程创建守护进程
-
守护进程会在主程序代码执行结束后就终止
-
守护进程内无法再开启子进程,否则抛出异常:
-
进程之间相互独立,主代码运行结束,守护进程随之终止
-
from multiprocessing import Process import time import random class Piao(Process): def __init__(self,name): self.name=name super().__init__() def run(self): print('%s is piaoing' %self.name) time.sleep(random.randrange(1,3)) print('%s is piao end' %self.name) p=Piao('egon') p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行 p.start() print('主')
-
-
-
6.互斥锁
-
from multiprocessing import Process from multiprocessing import Lock #互斥锁 import time import random def task1(lock): lock.acquire() print('task1:开始打印') time.sleep(random.randint(1,3)) print('task1:打印完成') lock.release() def task2(lock): lock.acquire() print('task2:开始打印') time.sleep(random.randint(1,3)) print('task2:打印完成') lock.release() def task3(lock): lock.acquire() print('task3:开始打印') time.sleep(random.randint(1,3)) print('task3:打印完成') lock.release() if __name__ == "__main__": lock = Lock() p1 = Process(target=task1,args=(lock,)) p2 = Process(target=task2,args=(lock,)) p3 = Process(target=task3,args=(lock,)) p2.start() p1.start() p3.start()
7.进程之间的通信:队列
-
什么是队列
- 队列就是存在于内存中的一个容器,最大的一个特点就是FIFO,也就是完全支持先进先出原则
-
为什么使用队列
- 互斥锁虽然能通过文件操作实现不同进程间的通信,但是效率低,并且自己加锁很麻烦,很容易出现递归锁,死锁
-
from multiprocessing import Queue q = Queue(3) #可以设置元素个数 q.put('alex') q.put('alex') q.put('alex') print(q.get()) print(q.get()) print(q.get()) # print(q.get(block = False))直接报错 # print(q.get(timeout=3))延时报错
-
其他参数
-
maxsize() q = Queue数据量不宜过大,最好是精简的重要的数据
-
putget block 默认为True,当你插入的数据超过最大限度,默认阻塞
-
# q = Queue(3) # 可以设置元素个数 # # q.put('alex') # q.put({'count': 1}) # q.put(22) # # q.put(333,block=False) # 改成False 数据超过最大限度,不阻塞了直接报错.
-
-
put/get timeout() 参数:延时报错,超过设定参数的时间再put数据,就会报错
-
8.进程之间的通信实例
-
利用队列进行集成之间的通信,简单方便,不用自己手动加锁,队列自带阻塞,可持续化取数据
-
# 小米:抢手环4.预期发售10个. # 有100个人去抢. import os from multiprocessing import Queue from multiprocessing import Process def task(q): try: q.put(f'{os.getpid()}',block=False) except Exception: return if __name__ == '__main__': q = Queue(10) for i in range(100): p = Process(target=task,args=(q,)) p.start() for i in range(1,11): print(f'排名第{i}的用户: {q.get()}',)
-
9.生产者消费者模型
-
生产者消费者模型:合理的去调控多个进程去生成数据以及提取数据,中间有个必不可少的环节容器队列
-
模型:设计模式,归一化设计,理论等等,是一个编程思路,可直接套用
-
生产者:生产数据进程
-
消费者:对生产者生产出的数据进行进一步的处理进程
-
容器:存放数据的容器.
-
为什么要有容器
- 如果没有容器,生产者与消费者强耦合性,不合理,所以我们要有一个容器,缓冲区,平衡了生产力和消费力
-
生产者消费者多用于并发
-
from multiprocessing import Process from multiprocessing import Queue import time import random # def producer(q): # time.sleep(3) # q.put('包子') # # for i in range(5): # # # # # def consumer(q): # print(q.get()) def producer(name,q): for i in range(1,6): time.sleep(random.randint(1,3)) res = f'{i}号包子' q.put(res) print(f'\033[0;32m 生产者{name}: 生产了{res}\033[0m') def consumer(name,q): while 1: try: time.sleep(random.randint(1,3)) ret = q.get(timeout=5) print(f'消费者{name}: 吃了{ret}') except Exception: return if __name__ == '__main__': q = Queue() p1 = Process(target=producer, args=('太白',q)) p2 = Process(target=consumer, args=('MC骚强',q)) p1.start() p2.start()
-