资深程序员带你学Python!深入 Python 多进程编程基础!

资深程序员带你学Python!深入 Python 多进程编程基础!,第1张

概述<pstyle=\"margin:10pxauto;color:rgb(57,57,57);font-family:verdana,\'mssong\',Arial,Helvetica,sans-serif;font-size:14px;text-align:left;background-color:rgb(250,247,239);\">多进程编程

<p >多进程编程知识是Python程序员进阶高级的必备知识点,我们平时习惯了使用multiprocessing库来 *** 纵多进程,但是并不知道它的具体实现原理。下面我对多进程的常用知识点都简单列了一遍,使用原生的多进程方法调用,帮助读者理解多进程的实现机制。代码跑在linux环境下。没有linux条件的,可以使用docker或者虚拟机运行进行体验。

<pre ><span >docker pull python:<span >2.<span >7
<h2 >生成子进程<p >Python生成子进程使用 <code >os.fork() ,它将产生一个子进程。fork调用同时在父进程和主进程同时返回,在父进程中返回子进程的pID,在子进程中返回0,如果返回值小于零,说明子进程产生失败,一般是因为 *** 作系统资源不足。

<code ><span  >import os
def create_child():
pID = os.fork()
<span >if pid > <span >0:
<span >print <span >'in father process'
<span >return <span >True
<span >elif pID == <span >0:
<span >'in child process'
<span >False
<span >else:
<span >raise
<h2 >生成多个子进程<p >我们调用 <code >create_child 方法多次就可以生成多个子进程,前提是必须保证 <code >create_child 是在父进程里执行,如果是子进程,就不要在调用了。

<pre ># Coding: utf<span >-8
# child.py
import os

def create_child(i):
pID = <span >os.fork()
<span >if pid > <span >0:
<span >return pID
elif pID == <span >0:
<span >'in child process',i
<span >return <span >0
<span >else:
raise

for i <span >in range(<span >10): # 循环<span >10次,创建<span >10个子进程
pID = create_child(i)
# pID==<span >0是子进程,应该立即退出循环,否则子进程也会继续生成子进程
# 子子孙孙,那就生成太多进程了
<span >if pID == <span >0:
<span >break
<p >运行 <code >python child.py ,输出

<pre ><span >in father process
in father process
in child <span >process <span >0
<span >in child <span >process <span >1
<span >in father process
in child <span >process <span >2
<span >process <span >3
<span >process <span >4
<span >process <span >5
<span >process <span >6
<span >process <span >7
<span >process <span >8
<span >process <span >9
<h2 >进程休眠<p >使用time.sleep可以使进程休眠任意时间,单位为秒,可以是小数

<pre >import time
for i in <span >range(<span >5):
<span >'hello'
time.sleep(<span >1) # 睡<span >1s
<h2 >杀死子进程<p >使用os.kill(pID,sig_num)可以向进程号为pID的子进程发送信号,sig_num常用的有SIGKILL(暴力杀死,相当于kill -9),SIGTERM(通知对方退出,相当于kill不带参数),SIGINT(相当于键盘的ctrl+c)。

<pre ># codin<span >g: utf-<span >8
# kill.py

import os
import time
import signal


def create_child():
pID = os.fork()
<span >return pID
elif pID == <span >0:
<span >else:
raise


pID = create_child()
<span >while True: # 子进程死循环打印字符串
<span >'in child process'
time.<span >sleep(<span >1)
<span >'in father process'
time.<span >sleep(<span >5) # 父进程休眠<span >5s再杀死子进程
os.kill(pID,signal.SIGKILL)
time.<span >sleep(<span >5) # 父进程继续休眠<span >5s观察子进程是否还有输出
<p >运行 <code >python kill.py ,我们看到控制台输出如下

<pre >in father process
in child process
# 等<span >1s
in child process
# 等<span >1s
in child process
# 等<span >1s
in child process
# 等<span >1s
in child process
# 等了<span >5s
<p >说明os.kill执行之后,子进程已经停止输出了

<h2 >僵尸子进程<p >在上面的例子中,os.kill执行完之后,我们通过ps -ef|grep python快速观察进程的状态,可以发现子进程有一个奇怪的显示 <code >

<pre >root <span >12 <span >1 <span >0 <span >11:<span >22 <span >pts/<span >0 <span >00:<span >00:<span >00 <span >python kill.py
root <span >13 <span >12 <span >0 <span >11:<span >22 <span >pts/<span >0 <span >00:<span >00:<span >00 [<span >python] <span ><<span >defunct<span >>
<p >待父进程终止后,子进程也一块消失了。那 <code > 是什么含义呢?

<p >它的含义是「僵尸进程」。子进程结束后,会立即成为僵尸进程,僵尸进程占用的 *** 作系统资源并不会立即释放,它就像一具尸体啥事也不干,但是还是持续占据着 *** 作系统的资源(内存等)。

<h2 >收割子进程<p >如果彻底干掉僵尸进程?父进程需要调用waitpID(pID,options)函数,「收割」子进程,这样子进程才可以灰飞烟灭。waitpID函数会返回子进程的退出状态,它就像子进程留下的临终遗言,必须等父进程听到后才能彻底瞑目。

<pre ># codin<span >g: utf-<span >8
import os
import time
import signal
def create_child():
pID = os.fork()
<span >else:
raise
pID = create_child()
<span margin:0px;padding:0px;" /><span >ret = os.waitpID(pID,<span >0) # 收割子进程
<span >print <span >ret # 看看到底返回了什么
time.<span >sleep(<span >5) # 父进程继续休眠<span >5s观察子进程是否还存在
<p >运行python kill.py输出如下

<pre >in father process
in child process
in child process
in child process
in child process
in child process
in child <span >process
(<span >125,<span >9)
<p >我们看到waitpID返回了一个tuple,第一个是子进程的pID,第二个9是什么含义呢,它在不同的 *** 作系统上含义不尽相同,不过在Unix上,它通常的value是一个16位的整数值,前8位表示进程的退出状态,后8位表示导致进程退出的信号的整数值。所以本例中退出状态位0,信号编号位9,还记得 <code >kill -9 这个命令么,就是这个9表示暴力杀死进程。

<p >如果我们将os.kill换一个信号才看结果,比如换成os.kill(pID,signal.SIGTERM),可以看到返回结果变成了 <code >(138,15) ,15就是SIGTERM信号的整数值。

<p ><code >waitpID(pID,0) 还可以起到等待子进程结束的功能,如果子进程不结束,那么该调用会一直卡住。

<h2 >捕获信号<p >SIGTERM信号默认处理动作就是退出进程,其实我们还可以设置SIGTERM信号的处理函数,使得它不退出。

<pre >if pID == <span >0:
signal.signal(signal.SIGTERM,signal.SIG_IGN)
<span margin:0px;padding:0px;" /> time.<span >sleep(<span >5) # 父进程继续休眠<span >5s观察子进程是否还存在
os.kill(pID,signal.SIGKILL) # 发一个SIGKILL信号
time.<span >sleep(<span >5) # 父进程继续休眠<span >5s观察子进程是否还存在
<p >我们在子进程里设置了信号处理函数,SIG_IGN表示忽略信号。我们发现第一次调用os.kill之后,子进程会继续输出。说明子进程没有被杀死。第二次os.kill之后,子进程终于停止了输出。

<p >接下来我们换一个自定义信号处理函数,子进程收到SIGTERM之后,打印一句话再退出。

<pre ># codin<span >g: utf-<span >8
import os
import sys
import time
import signal
def create_child():
pID = os.fork()
<span >else:
raise
def i_will_dIE(sig_num,frame): # 自定义信号处理函数
<span >"child will dIE"
sys.<span >exit(<span >0)
pID = create_child()
<span margin:0px;padding:0px;" /><span margin:0px;padding:0px;" /> time.<span >输出如下

<pre >in father process
in child process
in child process
in child process
in child process
in child process
child will dIE
<p >信号处理函数有两个参数,第一个sig_num表示被捕获信号的整数值,第二个frame不太好理解,一般也很少用。它表示被信号打断时,Python的运行的栈帧对象信息。读者可以不必深度理解。

<h2 >多进程并行计算实例<p >下面我们使用多进程进行一个计算圆周率PI。对于圆周率PI有一个数学极限公式,我们将使用该公司来计算圆周率PI。

<p >

<p >先使用单进程版本

<pre >import math
def pi(n):
s = 0.0
for i in range(n):
s += 1.0/(2i+1)/(2i+1)
return math.sqrt(8 s)
print pi(10000000)
<p >输出

<code >3<span  >.14159262176
<p >这个程序跑了有一小会才出结果,不过这个值已经非常接近圆周率了。

<p >接下来我们用多进程版本,我们用redis进行进程间通信。

<pre ># codin<span >g: utf-<span >8
import os
import sys
import math
import redis
def slice(mink,maxk):
s = <span >0.0
<span >for <span >k in <span >range(mink,maxk):
s += <span >1.0/(<span >2
<span >k+<span >1)/(<span >2<span >k+<span >1)
<span >return s
def pi(n):
pIDs = []
unit = n / <span >10
clIEnt = redis.StrictRedis()
clIEnt.<span >delete(<span >"result") # 保证结果集是干净的
del clIEnt # 关闭连接
<span >for i in <span >range(<span >10): # 分<span >10个子进程
mink = unit
i
maxk = mink + unit
pID = os.fork()
<span >if pid > <span >0:
pIDs.<span >append(pID)
<span >else:
s = slice(mink,maxk) # 子进程开始计算
clIEnt = redis.StrictRedis()
clIEnt.rpush(<span >"result",str(s)) # 传递子进程结果
sys.<span >exit(<span >0) # 子进程结束
<span >for pID in pID<span >s:
os.waitpID(pID,<span >0) # 等待子进程结束
sum = <span >0
clIEnt = redis.StrictRedis()
<span >for s in clIEnt.lrange(<span hlJs-number" >0,-<span >1):
sum += float(s) # 收集子进程计算结果
<span >return math.<span >sqrt(sum * <span >8)
<span >print pi(<span >10000000)
<p >我们将级数之和的计算拆分成10个子进程计算,每个子进程负责1/10的计算量,并将计算的中间结果扔到redis的队列中,然后父进程等待所有子进程结束,再将队列中的数据全部汇总起来计算最终结果。

<p >输出如下

<code >3<span  >.14159262176
<p >这个结果和单进程结果一致,但是花费的时间要缩短了不少。

<p >这里我们之所以使用redis作为进程间通信方式,是因为进程间通信是一个比较复杂的技术,我们需要单独一篇文章来仔细讲,各位读者请耐心听我下回分解,我们将会使用进程间通信技术来替换掉这里的redis。

<p >欢迎关注我的博客或者微信公众号:Python学习交流

<p >换人加入我的千人交流学习群:125240963

总结

以上是内存溢出为你收集整理的资深程序员带你学Python!深入 Python 多进程编程基础!全部内容,希望文章能够帮你解决资深程序员带你学Python!深入 Python 多进程编程基础!所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1209021.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存