Python公开课 - 多进程使用

前言

大家使用计算机,会天天和进程打交道,例如打开一个chrome浏览器去上网看新闻,在windows的任务管理器里面就会看到有google chrome这个进程。

按书面语来解释进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

这里主要有两个概念:

  1. 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
  2. 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体。

多进程使用场景

在代码开发中,我们也会以多进程的方式来提供程序的并发性,提高执行效率,另外在有些情况下,多进程架构也可以更保证程序更安全健壮,例如谷歌浏览器:

Google Chrome将插件或者网络应用放在与浏览器本身不同的进程中。在一个渲染引擎中的崩溃并不会影响浏览器本身或是其他网络应用。这意味着操作系统可以并发的运行网络应用来提高响应速度,如果一个特定的网络应用程序或是插件停止响应时浏览器本身并不会被死锁。

Python中多进程使用

linux下可使用 fork 函数

import os

print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid==0:
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
    os._exit(1)
else:
    print('I (%s) just created a child process (%s).' % (os.getpid(), pid))


输出:
Process (17601) start...
I (17601) just created a child process (17602).
I am child process (17602) and my parent is 1.

一个fork()属于系统调用,它比较特殊。调用一次,返回两次,因为操作系统自动把当前进程和子进程进行返回。子进程永远返回0,而父进程返回子进程的ID。

使用 multiprocessing

from multiprocessing import Process
import os
import time

def run_proc(name):
    time.sleep(3)
    print ('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print ('Parent process %s.' % os.getpid())
    processes = []
    for i in range(5):
        p = Process(target=run_proc, args=('test',))
        print ('Process will start.')
        p.start()
        processes.append(p)

    for p in processes:
        p.join()
    print ('Process end.')


输出:
Parent process 18259.
Process will start.
Process will start.
Process will start.
Process will start.
Process will start.
Run child process test (18262)...
Run child process test (18261)...
Run child process test (18263)...
Run child process test (18260)...
Run child process test (18264)...
Process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。

join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

进程池

如果资源有限,一般会进行池化处理,这里的感念与线程池、内存池其实是一样的。

如果要启动大量的子进程,可以用进程池的方式批量创建子进程。

使用 multiprocessing.Pool 非阻塞

import multiprocessing
import time

def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in range(3):
        msg = "hello %d" %(i)
        pool.apply_async(func, (msg, ))

    print("M~ M~ M~~~~~~~~~~~~~~~~~~~~~~")
    pool.close()
    pool.join()
    print("Sub-process(es) done.")


输出:
M~ M~ M~~~~~~~~~~~~~~~~~~~~~~
msg: hello 0
msg: hello 1
msg: hello 2
end
end
end
Sub-process(es) done.

使用 multiprocessing.Pool 阻塞版本

import multiprocessing
import time

def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in range(3):
        msg = "hello %d" %(i)
        pool.apply(func, (msg, ))

    print("M~ M~ M~~~~~~~~~~~~~~~~~~~~~~")
    pool.close()
    pool.join()
    print("Sub-process(es) done.")

输出:
msg: hello 0
end
msg: hello 1
end
msg: hello 2
end
M~ M~ M~~~~~~~~~~~~~~~~~~~~~~
Sub-process(es) done.

区别主要是 apply_async和apply函数,前者是非阻塞的,后者是阻塞

小结

在Unix/Linux下,可以使用fork()调用实现多进程。

要实现跨平台的多进程,可以使用multiprocessing模块。

相关阅读