一 multiprocessing 模块介绍
二 process 类的介绍
三 process 类的使用
四 守护进程
五 进程同步(锁)
六 队列
七 管道
八 共享数据
九 信号量
十 事件
十一 进程池
一 multiprocessing 模块介绍
python 中的多线程无法利用多核优势, 如果想要充分地使用多核 CPU 的资源 (os.cpu_count() 查看), 在 python 中大部分情况需要使用多进程 Python 提供了 multiprocessing
multiprocessing 模块用来开启子进程, 并在子进程中执行我们定制的任务(比如函数), 该模块与多线程模块 threading 的编程接口类似
multiprocessing 模块的功能众多: 支持子进程通信和共享数据执行不同形式的同步, 提供了 ProcessQueuePipeLock 等组件
需要再次强调的一点是: 与线程不同, 进程没有任何共享状态, 进程修改的数据, 改动仅限于该进程内
二 process 类的介绍
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]]), 由该类实例化得到的对象, 表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args 指定的为传给 target 函数的位置参数, 是一个元组形式, 必须有逗号
参数介绍:
group 参数未使用, 值始终为 None
target 表示调用对象, 即子进程要执行的任务
args 表示调用对象的位置参数元组, args=(1,2,egon,)
kwargs 表示调用对象的字典, kwargs={name:egon,age:18}
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 开启的进程
属性介绍:
p.daemon: 默认值为 False, 如果设为 True, 代表 p 为后台运行的守护进程, 当 p 的父进程终止时, p 也随之终止, 并且设定为 True 后, p 不能创建自己的新进程, 必须在 p.start()之前设置
p.name: 进程的名称
p.pid: 进程的 pid
p.exitcode: 进程在运行时为 None 如果为 N, 表示被信号 N 结束(了解即可)
p.authkey: 进程的身份验证键, 默认是由 os.urandom()随机生成的 32 字符的字符串这个键的用途是为涉及网络连接的底层进程间通信提供安全性, 这类连接只有在具有相同的身份验证键时才能成功(了解即可)
三 process 类的使用
注意: 在 windows 中 Process()必须放到# if __name__ == __main__: 下
- Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module.
- If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources).
- This is the reason for hiding calls to Process() inside
- if __name__ == "__main__"
- since statements inside this if-statement will not get called upon import.
由于 Windows 没有 fork, 多处理模块启动一个新的 Python 进程并导入调用模块
如果在导入时调用 Process(), 那么这将启动无限继承的新进程(或直到机器耗尽资源)
这是隐藏对 Process()内部调用的原, 使用 if __name__ == __main __, 这个 if 语句中的语句将不会在导入时被调用
创建并开启子进程的两种方式
- # 开进程的方法一:
- import time
- import random
- from multiprocessing import Process
- def piao(name):
- print(%s piaoing %name)
- time.sleep(random.randrange(1,5))
- print(%s piao end %name)
- p1=Process(target=piao,args=(egon,)) #必须加, 号
- p2=Process(target=piao,args=(alex,))
- p3=Process(target=piao,args=(wupeqi,))
- p4=Process(target=piao,args=(yuanhao,))
- p1.start()
- p2.start()
- p3.start()
- p4.start()
print(主线程)
方法一
- # 开进程的方法二:
- import time
- import random
- from multiprocessing import Process
- class Piao(Process):
- def __init__(self,name):
- super().__init__()
- self.name=name
- def run(self):
- print(%s piaoing %self.name)
- time.sleep(random.randrange(1,5))
- print(%s piao end %self.name)
- p1=Piao(egon)
- p2=Piao(alex)
- p3=Piao(wupeiqi)
- p4=Piao(yuanhao)
- p1.start() #start 会自动调用 run
- p2.start()
- p3.start()
- p4.start()
print(主线程)
方法二
进程直接的内存空间是隔离的
- from multiprocessing import Process
- n=100 #在 windows 系统中应该把全局变量定义在 if __name__ == __main__之上就可以了
- def work():
- global n
- n=0
print(子进程内: ,n)
- if __name__ == __main__:
- p=Process(target=work)
- p.start()
print(主进程内: ,n)
Process 对象的 join 方法
- 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.start()
- p.join(0.0001) #等待 p 停止, 等 0.0001 秒就不再等了
print(开始)
join: 主进程等, 等待子进程结束
- from multiprocessing import Process
- import time
- import random
- def piao(name):
- print(%s is piaoing %name)
- time.sleep(random.randint(1,3))
- print(%s is piao end %name)
- p1=Process(target=piao,args=(egon,))
- p2=Process(target=piao,args=(alex,))
- p3=Process(target=piao,args=(yuanhao,))
- p4=Process(target=piao,args=(wupeiqi,))
- p1.start()
- p2.start()
- p3.start()
- p4.start()
- #有的同学会有疑问: 既然 join 是等待进程结束, 那么我像下面这样写, 进程不就又变成串行的了吗?
- #当然不是了, 必须明确: p.join()是让谁等?
- #很明显 p.join()是让主线程等待 p 的结束, 卡住的是主线程而绝非进程 p,
- #详细解析如下:
- #进程只要 start 就会在开始运行了, 所以 p1-p4.start()时, 系统中已经有四个并发的进程了
- #而我们 p1.join()是在等 p1 结束, 没错 p1 只要不结束主线程就会一直卡在原地, 这也是问题的关键
- #join 是让主线程等, 而 p1-p4 仍然是并发执行的, p1.join 的时候, 其余 p2,p3,p4 仍然在运行, 等 #p1.join 结束, 可能 p2,p3,p4 早已经结束了, 这样 p2.join,p3.join.p4.join 直接通过检测, 无需等待
- # 所以 4 个 join 花费的总时间仍然是耗费时间最长的那个进程运行的时间
- p1.join()
- p2.join()
- p3.join()
- p4.join()
print(主线程)
- #上述启动进程与 join 进程可以简写为
- # p_l=[p1,p2,p3,p4]
- #
- # for p in p_l:
- # p.start()
- #
- # for p in p_l:
- # p.join()
有了 join, 程序不就是串行了吗?
僵尸进程与孤儿进程(了解)
参考博客: 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
来源: http://www.bubuko.com/infodetail-2501785.html