一 多进程multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
二 Process类的介绍
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
target-表示定义函数的函数名,name-不需要指定,args,kwargs 表示参数名,args是用tuple元祖存值,kwargs是用字典存值。
强调: 1. 需要使用关键字的方式来指定参数 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 p.ppid 父进程的id
7 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
8
9 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,
这类连接只有在具有相同的身份验证键时才能成功(了解即可)
三 Process类的使用
注意:在windows中Process()必须放到# if __name__ == '__main__':下
创建并开启子进程的两种方式
1 from multiprocessing import Process
2 import time
3
4 class task(name,time_second):
5 print('%s is running'%name)
6 time.sleep(time_second)
7 print('%s is done%name)
8
9 if __name__ == '__main__':
10 child_process1 = Process(target=task,args = (IsEtan,3)
11 child_process2 = Process(target=task,args = (IsEtan,3)
12 child_process3 = Process(target=task,args = (IsEtan,3)
13
14 child_process1.start()
15 child_process2.start()
16 child_process3.start()
17
18 child_process1.join()
19 child_process2.join()
20 child_process3.join()
multiprocess实现丑陋的并发
1 from multiprocess import Process
2
3 import time
4
5 class Myprocess(Process):
6
7 def __init__(self,name):
8
9 super().__init__
10 self.name = name
11
12 def run(self):
13 print('%s is running'%self.name)
14 time.sleep(3)
15 print('%s is stop'%self.name)
16
17 if __name__ == '__mian__':
18 child_process = Myprocess('child process')
19 child.start()
20 child.join()
21 print('主')
自定义类实现并发
1 from multiprocessing import Process
2 n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
3 def work():
4 global n
5 n=0
6 print('子进程内: ',n)
7
8
9 if __name__ == '__main__':
10 p=Process(target=work)
11 p.start()
12 print('主进程内: ',n)
字进程与父进程之间的物理内存空间是隔离的
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('开始')
父进程等字进程结束后回收PID号等信息
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()
有了join程序还是并发,不是串行,仅仅是父进程在等字进程结束而已
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,terminate与start相似仅仅是像操作系统发出一个关闭请求,何时关闭进程由操作系统决定
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
查看子进程号、父进程号、进程名字
1 参考博客:http://www.cnblogs.com/Anker/p/3271773.html
2
3 一:僵尸进程(有害)
4 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下
5
6 我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。
7
8 因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
9 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
10 2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
11
12 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
13
14 二:孤儿进程(无害)
15
16 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
17
18 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
19
20 我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容
21
22 import os
23 import sys
24 import time
25
26 pid = os.getpid()
27 ppid = os.getppid()
28 print 'im father', 'pid', pid, 'ppid', ppid
29 pid = os.fork()
30 #执行pid=os.fork()则会生成一个子进程
31 #返回值pid有两种值:
32 # 如果返回的pid值为0,表示在子进程当中
33 # 如果返回的pid值>0,表示在父进程当中
34 if pid > 0:
35 print 'father died..'
36 sys.exit(0)
37
38 # 保证主线程退出完毕
39 time.sleep(1)
40 print 'im child', os.getpid(), os.getppid()
41
42 执行文件,输出结果:
43 im father pid 32515 ppid 32015
44 father died..
45 im child 32516 1
46
47 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。
48
49
50 三:僵尸进程危害场景:
51
52 例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
53
54 四:测试
55 #1、产生僵尸进程的程序test.py内容如下
56
57 #coding:utf-8
58 from multiprocessing import Process
59 import time,os
60
61 def run():
62 print('子',os.getpid())
63
64 if __name__ == '__main__':
65 p=Process(target=run)
66 p.start()
67
68 print('主',os.getpid())
69 time.sleep(1000)
70
71
72 #2、在unix或linux系统上执行
73 [root@vm172-31-0-19 ~]# python3 test.py &
74 [1] 18652
75 [root@vm172-31-0-19 ~]# 主 18652
76 子 18653
77
78 [root@vm172-31-0-19 ~]# ps aux |grep Z
79 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
80 root 18653 0.0 0.0 0 0 pts/0 Z 20:02 0:00 [python3] <defunct> #出现僵尸进程
81 root 18656 0.0 0.0 112648 952 pts/0 S+ 20:02 0:00 grep --color=auto Z
82
83 [root@vm172-31-0-19 ~]# top #执行top命令发现1zombie
84 top - 20:03:42 up 31 min, 3 users, load average: 0.01, 0.06, 0.12
85 Tasks: 93 total, 2 running, 90 sleeping, 0 stopped, 1 zombie
86 %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
87 KiB Mem : 1016884 total, 97184 free, 70848 used, 848852 buff/cache
88 KiB Swap: 0 total, 0 free, 0 used. 782540 avail Mem
89
90 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
91 root 20 0 29788 1256 988 S 0.3 0.1 0:01.50 elfin
92
93
94 #3、
95 等待父进程正常结束后会调用wait/waitpid去回收僵尸进程
96 但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的
97 解决方法一:杀死父进程
98 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
99 参考python2源码注释
100 class Process(object):
101 def join(self, timeout=None):
102 '''
103 Wait until child process terminates
104 '''
105 assert self._parent_pid == os.getpid(), 'can only join a child process'
106 assert self._popen is not None, 'can only join a started process'
107 res = self._popen.wait(timeout)
108 if res is not None:
109 _current_process._children.discard(self)
110
111 join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除
112
113 解决方法三:
僵尸进程与孤儿进程(了解)
# -*- coding: utf-8 -*-
# @Time : 2018/7/11 0011 下午 3:56
# @File : 03-查看进程号和进程名字.py
# @Author : IsEtan
from multiprocessing import Process,current_process
import time
import os
#1
def process(process_name):
print('%s is running,进程名字%s'%(os.getpid(),current_process().name))
time.sleep(3)
print('%s is ending,%s'%(os.getpid(),os.getppid()))
if __name__ == '__main__':
process_obj1 = Process(target=process,args=('创建的子进程',))
process_obj2 = Process(target=process,args=('创建的子进程',))
process_obj1.start()
process_obj2.start()
print(process_obj1.pid)
print(process_obj1.name)
print(process_obj2.pid)
print(process_obj2.name)
print('---->ppid',os.getppid())
### current_process().name == 父类创建的对象process_obj1.name
### process_obj1.pid = os.getpid()
### 查看父进程的ID只能用os.getppid
查看进程号以及进程名称
import time
def task(name,n):
print('%s is running ' %name)
time.sleep(n)
print('%s is done ' % name)
if __name__ == '__main__':
p_l=[]
start=time.time()
for i in range(1,4):
p=Process(target=task,args=('子进程%s' %i,i))
p_l.append(p)
p.start()
for p in p_l:
p.join()
stop=time.time()
for 循环生成多个子进程