python

python 多进程(一)multiprocessing.Pro

2019-08-21  本文已影响0人  eeert2

该文章基于 python3.7,部分功能只有python3.7能实现

一、 进程模块multiprocessing

多进程可以实现多个程序的并行,充分利用计算机的资源,在不同的平台/操作系统上,python实现多进程的方式不同

在Unix/Linux 中,通过fork()调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
    print('I (%s) just created a child process (%s).' % (os.getpid(), pid))

运行结果如下:

Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.

由于Windows没有fork调用,上面的代码在Windows上无法运行,我们可以使用multiprocessing模块,其封装了底层复制进程的过程,Unix 和 Windows 上都可以运行。

根据不同的平台, multiprocessing 支持三种启动进程的方法。

二、进程对象Process

进程模块multiprocessing中包含与进程相关的异常、同步、通信等等相关,其中Process封装了进程对象的相关API,是一个子进程的物化实现,封装了子进程状态与管理相关功能。

  1. 如何创建一个子进程对象
Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

应始终使用关键字参数调用构造函数,而不是 None, None, p1……这样来传入参数,否则可能造成不可知错误。

示例:创建子进程,并显示子进程和父进程的的进程ID

from multiprocessing import Process
import os


def child_main(name):
    print('I am', name, 'process id:', os.getpid())
    print('parent process:', os.getppid())


if __name__ == '__main__':
    print('main process id:', os.getpid())
    p = Process(target=child_main, args=('bob',))
    p.start()
    p.join()

打印结果

main process id: 2649
I am bob process id: 2650
parent process: 2649

从程序运行来看

p = Process(target=child_main, args=('bob',))

创建了一个子进程,该子进程执行child_main方法,方法的参数为'bob'p为该子进程对象的物化实现,封装子进程相关的功能。

  1. Process对象的常用方法

注意 start()join()is_alive()terminate()exitcode 方法只能由创建进程对象的进程调用。

使用实例 1,利用多进程实现timeout 函数

在使用 爬虫 的相关技术中,有很多方法都具有 timeout 参数,也可以利用多进程实现timeout 函数,思路如下:
将运行函数放到子进程中运行,在主进程中等待子进程执行join([timeout]),然后判断子进程的状态 is_alive(),如果为真,说明子进程还在运行,已经超过我们的限制时间,则打断子进程,并在主进程中抛出异常。
实现如下:
这里在函数 run_limit()中包含一个辅助函数,我们也可以将辅助函数作为一个参数,可以实现对所有函数转成timeout 函数

from multiprocessing import Process
import time


def run_limit(timeout=5):
    def fun():
        i = 0
        while True:
            time.sleep(1)
            i += 1
            print(i)

    p = Process(target=fun, )
    p.start()
    p.join(timeout=timeout)
    if p.is_alive():
        p.terminate()
        raise TimeoutError(f'运行超时{timeout}!')


if __name__ == '__main__':
    run_limit()

注意,这里每执行该函数都会启动一个子进程,较普通函数有较大的消耗。

使用实例 2,利用守护进程daemon实现心跳机制

背景:

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。

在Linux系统中,计算机刚启动时只有一个进程,PID为1,名字为init(centos6系统)或者systemd(centos7系统),systemd进程通过复制自身进程启动了其它进程,后面再复制出整个计算机进程,所以进程被设计成独立的,也就是说父进程关闭了,子进程照样能正常运行,这是非常必要的,不至于其中一个进程挂了,让它的子孙后代进程都挂掉。

但如果我们在主进程中开启一个子进程用于向远方服务器报告,这种进程间的独立就不符合我们的期望,而daemon参数可以实现主进程与子进程的绑定,主进程结束,守护进程的子进程也结束,如下

from multiprocessing import Process
import time


def child_main():
    while True:
        print('i am live')
        time.sleep(3)


if __name__ == '__main__':
    p = Process(target=child_main, daemon=True)
    p.start()
    time.sleep(10)
    print('main end')

这里没有使用join()方法等待子进程,所以在运行10秒后主进程结束,
由于参数daemon=True ,所以子进程在主进程结束后也跟着结束了。

上一篇下一篇

猜你喜欢

热点阅读