博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入Python多进程编程基础
阅读量:2440 次
发布时间:2019-05-10

本文共 6518 字,大约阅读时间需要 21 分钟。

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

docker pull python:2.7

生成子进程

Python生成子进程使用os.fork(),它将产生一个子进程。fork调用同时在父进程和主进程同时返回,在父进程中返回子进程的pid,在子进程中返回0,如果返回值小于零,说明子进程产生失败,一般是因为操作系统资源不足。

import osdef create_child():    pid = os.fork()    if pid > 0:        print 'in father process'        return True    elif pid == 0:        print 'in child process'        return False    else:        raise

生成多个子进程

我们调用create_child方法多次就可以生成多个子进程,前提是必须保证create_child是在父进程里执行,如果是子进程,就不要在调用了。

# coding: utf-8# child.pyimport osdef create_child(i):    pid = os.fork()    if pid > 0:        print 'in father process'        return pid    elif pid == 0:        print 'in child process', i        return 0    else:        raisefor i in range(10):  # 循环10次,创建10个子进程    pid = create_child(i)    # pid==0是子进程,应该立即退出循环,否则子进程也会继续生成子进程    # 子子孙孙,那就生成太多进程了    if pid == 0:        break

运行python child.py,输出

in father processin father processin child process 0in child process 1in father processin child process 2in father processin father processin child process 3in father processin child process 4in child process 5in father processin father processin child process 6in child process 7in father processin child process 8in father processin child process 9

进程休眠

使用time.sleep可以使进程休眠任意时间,单位为秒,可以是小数

import timefor i in range(5):    print 'hello'    time.sleep(1)  # 睡1s

杀死子进程

使用os.kill(pid, sig_num)可以向进程号为pid的子进程发送信号,sig_num常用的有SIGKILL(暴力杀死,相当于kill -9),SIGTERM(通知对方退出,相当于kill不带参数),SIGINT(相当于键盘的ctrl+c)。

# coding: utf-8# kill.pyimport osimport timeimport signaldef create_child():    pid = os.fork()    if pid > 0:        return pid    elif pid == 0:        return 0    else:        raisepid = create_child()if pid == 0:    while True:  # 子进程死循环打印字符串        print 'in child process'        time.sleep(1)else:    print 'in father process'    time.sleep(5)  # 父进程休眠5s再杀死子进程    os.kill(pid, signal.SIGKILL)    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还有输出

运行python kill.py,我们看到控制台输出如下

in father processin child process# 等1sin child process# 等1sin child process# 等1sin child process# 等1sin child process# 等了5s

说明os.kill执行之后,子进程已经停止输出了

僵尸子进程

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

root        12     1  0 11:22 pts/0    00:00:00 python kill.pyroot        13    12  0 11:22 pts/0    00:00:00 [python] 

待父进程终止后,子进程也一块消失了。那<defunct>是什么含义呢?

收割子进程

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

# coding: utf-8import osimport timeimport signaldef create_child():    pid = os.fork()    if pid > 0:        return pid    elif pid == 0:        return 0    else:        raisepid = create_child()if pid == 0:    while True:  # 子进程死循环打印字符串        print 'in child process'        time.sleep(1)else:    print 'in father process'    time.sleep(5)  # 父进程休眠5s再杀死子进程    os.kill(pid, signal.SIGTERM)    ret = os.waitpid(pid, 0)  # 收割子进程    print ret  # 看看到底返回了什么    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

运行python kill.py输出如下

in father processin child processin child processin child processin child processin child processin child process(125, 9)

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

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

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

捕获信号

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

# coding: utf-8import osimport timeimport signaldef create_child():    pid = os.fork()    if pid > 0:        return pid    elif pid == 0:        return 0    else:        raisepid = create_child()if pid == 0:    signal.signal(signal.SIGTERM, signal.SIG_IGN)    while True:  # 子进程死循环打印字符串        print 'in child process'        time.sleep(1)else:    print 'in father process'    time.sleep(5)  # 父进程休眠5s再杀死子进程    os.kill(pid, signal.SIGTERM)  # 发一个SIGTERM信号    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在    os.kill(pid, signal.SIGKILL)  # 发一个SIGKILL信号    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

我们在子进程里设置了信号处理函数,SIG_IGN表示忽略信号。我们发现第一次调用os.kill之后,子进程会继续输出。说明子进程没有被杀死。第二次os.kill之后,子进程终于停止了输出。

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

# coding: utf-8import osimport sysimport timeimport signaldef create_child():    pid = os.fork()    if pid > 0:        return pid    elif pid == 0:        return 0    else:        raisedef i_will_die(sig_num, frame):  # 自定义信号处理函数    print "child will die"    sys.exit(0)pid = create_child()if pid == 0:    signal.signal(signal.SIGTERM, i_will_die)    while True:  # 子进程死循环打印字符串        print 'in child process'        time.sleep(1)else:    print 'in father process'    time.sleep(5)  # 父进程休眠5s再杀死子进程    os.kill(pid, signal.SIGTERM)    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

输出如下

in father processin child processin child processin child processin child processin child processchild will die

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

多进程并行计算实例

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

640?wx_fmt=png

先使用单进程版本

import mathdef pi(n):    s = 0.0    for i in range(n):        s += 1.0/(2*i+1)/(2*i+1)    return math.sqrt(8 * s)print pi(10000000)

输出

3.14159262176

这个程序跑了有一小会才出结果,不过这个值已经非常接近圆周率了。

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

# coding: utf-8import osimport sysimport mathimport redisdef slice(mink, maxk):    s = 0.0    for k in range(mink, maxk):        s += 1.0/(2*k+1)/(2*k+1)    return sdef pi(n):    pids = []    unit = n / 10    client = redis.StrictRedis()    client.delete("result")  # 保证结果集是干净的    del client  # 关闭连接    for i in range(10):  # 分10个子进程        mink = unit * i        maxk = mink + unit        pid = os.fork()        if pid > 0:            pids.append(pid)        else:            s = slice(mink, maxk)  # 子进程开始计算            client = redis.StrictRedis()            client.rpush("result", str(s))  # 传递子进程结果            sys.exit(0)  # 子进程结束    for pid in pids:        os.waitpid(pid, 0)  # 等待子进程结束    sum = 0    client = redis.StrictRedis()    for s in client.lrange("result", 0, -1):        sum += float(s)  # 收集子进程计算结果    return math.sqrt(sum * 8)print pi(10000000)

我们将级数之和的计算拆分成10个子进程计算,每个子进程负责1/10的计算量,并将计算的中间结果扔到redis的队列中,然后父进程等待所有子进程结束,再将队列中的数据全部汇总起来计算最终结果。

输出如下

3.14159262176

这个结果和单进程结果一致,但是花费的时间要缩短了不少。

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

640?wx_fmt=jpeg

阅读python相关高级文章,请关注公众号「码洞」

转载地址:http://itbqb.baihongyu.com/

你可能感兴趣的文章
FreeBSD安装文件系统(转)
查看>>
最简单FreeBSD网关方案(转)
查看>>
Windows 98 多用户的管理(转)
查看>>
更改Windows XP 的日期和时间(转)
查看>>
windows2000中的“秘密武器”(三)(转)
查看>>
Linux程序应用开发环境和工具经验谈(转)
查看>>
Linux办公一条龙之电子表格Calc(转)
查看>>
在NETBSD上配置ADSL+IPF+IPNAT(转)
查看>>
Windows 98 使用维护向导(转)
查看>>
用win2000收发传真(转)
查看>>
Linux办公一条龙之初识OpenOffice(转)
查看>>
Linux上安装GCC编译器过程(转)
查看>>
使用Windows XP 的任务计划(转)
查看>>
Linux分区工具的使用方法(转)
查看>>
深入理解硬盘的Linux分区(转)
查看>>
循序渐进教你LINUX之软件配置方法(转)
查看>>
NetBSD 指导手册(转)
查看>>
打造FreeBSD桌面系统(2)(转)
查看>>
为你的 Windows 98 加把锁(转)
查看>>
Windows 98 资源管理(转)
查看>>