原标题:Python多进程(中篇)
Python部落组织翻译,禁止转载,欢迎转发
这段代码中,在我们的工作进程中,我们使用一个简单的轮转机制分配工作。这并不总是最佳的或我们想要的。你可以看看ZeroMQ指南来了解其他的选择。
在我的测试例子中,我使用Node.js语言绑定,它可以通过NPM(https://www.npmjs.com/package/zmq)安装使用,和Python zmq语言绑定,它是Anaconda Python分配(https://store.continuum.io/cshop/anaconda/)的一部分。
在我的测试例子中,我启动客户端,代理和工作进程,它们每一个都有自己的bash会话窗口。
6.2.2 任务的负载均衡分配
先前的例子给一个工作进程分发一个任务,即使该工作进程还没有完成它之前的任务。在下面例子中,如果这个工作进程有一个任务,当它已经完成了该任务,并且为它的下一个任务准备就绪时,代理将向该工作进程发送一个请求。
同样的,展示如何从Node.js请求用Python实现的服务,我们的客户端用Node.js编写,代理和工作进程用Python编写。
下面是Node.js客户端:
注意:
注意,在未向代理发送请求之前,我们是如何启动监听进程的(sock.on("message" ...)。最初,我是在发送这些请求之后启动监听进程的,该版本中,收到的回复是断断续续的,丢失的,或者根本没有收到回复。
下面是用Python实现的代理:
下面是工作进程,也是用Python实现的:
注意:
我们的工作进程使用lxml来计算文档中的元素。事实上,这个例子的关键点是要表明,,我们可以从Node.js发送XML请求,在Python中进行处理。当然,还有其他种类的服务和处理,Python是一个不错的选择,我们希望能够用Node.js以及其他语言,例如,特别是用SciPy和NumPy的科学与数值处理,来发送请求。
你应该注意到,用其他的功能函数来完全替代这个工作进程所做的处理是琐碎的。一些提示:
ₒ 传入消息的有效负载是JSON;具体而言,它是一个字典,可以进行调整,以便携带其他参数。
ₒ 你可能想要在该字典中包含一个键值对,该字典中指定要调用几个函数中的一个键值。
7 Web服务器后台的并行处理
该部分论述了在Web服务器后台并行运行Python程序的几个策略。
7.1 Web服务器后台的IPython并行处理
7.2 nodejs后台的Python并行处理
当我们用Nodejs实现了一个Web站点,几乎不需要做额外的工作,Nodejs给我们提供了并行处理功能。这是因为,虽然Nodejs Web服务器在一个单线程中处理所有的请求,我们可以通过多个进程,使用Nodejs集群模块分发处理请求。Nodejs对每个HTTP请求(不包括Web Socket和AJAX请求?)都使用一个单独的进程。因此,如果我们使用Nodejs集群插件,我们得到单独的进程,并行处理和负载均衡。
Web应用开发不是本文档的目的,但是,在 http://nodejs.org 有很多的帮助和文档,还有很多相关的网站链接。
剩下要做的就是调用Python。由于NodeJS是用Java写的,这就需要调用某种外部的函数。一个解决办法是使用系统消息,例如ZeroMQ(http://zeromq.org/)。zerorpc,这是一个建立在ZeroMQ之上的包,更多有用信息(见: http://zerorpc.dotcloud.com/)。
下面是Java实例(运行在Nodejs上),调用了Python类中的一个方法:
下面是Python代码,可以由上面的实例调用:
剩下要做的就是要确保(1)每个Nodejs进程都有自己的Python进程(目的是,计算密集型,长时间运行的Python代码(例如,那些导致Numpy/SciPy的复杂调用)不用互相等待,不用因为相同的Python GIL(全局解释器锁)冲突导致进度缓慢)。(2)因为开启一个进程是很缓慢的,所以,Python进程一旦启动,保持它是活跃的。
8 Erlang + erlport + Python
Erlang包含多进程;Erlang可以使多进程进行通信;Erlang和Erlport可以创建Python进程,并且可以使其相互通信;因此,为什么不试着在Python中用某种Erlang控制器创建多进程呢?
一些说明:
Erlport / Python进程与内部的Erlang进程是不同的,我们能够使用Erlang进行创建和使用。Erlport / Python进程是操作系统的进程,而Erlang进程不是。它们比Erlang进程占的比重更大,所以,创建过程更缓慢。这意味着,如果我们想提出很多请求,我们将要创建一个进程池,并在有请求时重用它们。
Erlang编程是基于Actor模型的。这意味着,即使是本地的Erlang程序,是基于一个模型,该模型实现并创建独立的不共享资源(例如,内存)的进程,然后在这些进程之间传递消息。所以,多进程和远程/分布式处理应用程序实现的一种方式,即,外部(OS)进程之间互相发送消息,似乎与Erlang以及它的能力更适合。
本文档中我们分析了几个例子:
1.第一个是一个简单的例子,创建一个单一的Erlport / Python进程,发送请求和接收结果。
2.接下来我们将写一个Erlang程序,创造了一个erlport / Python进程池,发送一系列请求到一个可用的进程,但如果池中的所有进程都忙着,必须等待进程可用。
3.最后,我们将使用Erlang行为实现类似上面的进程池的流程。这样做的一项好处是,如果我们的其中一个Erlport / Python进程消亡,启动一个新的进程取代它。
我们所有的例子都使用相同的Python代码。如下:
注意:
erlport包必须放在Python可以引用的位置,必须安装Numpy和Scipy。同样的,我使用Continuum Analytics中的Anaconda分布(见: http://www.continuum.io/)。
函数test使用Numpy创建了几个由随机数组成的数组,然后,利用 Scipy中的linalg,直接使用Schur分解方法求解连续的代数Riccati方程,又称CARE,方程形式是(A"X + XA - XBR^-1B"X+Q=0)。
main函数调用test方法,然后,将解数组转换成一个列表后,返回一个包含Erlang原子“ok”的元组。在返回前,我们将Numpy数组转换成Python列表,因为Erlport能识别Python列表但不能识别Numpy数组。
8.1 从Erlang到Python的一个简单调用
下面是一个使用Python样例与Erlport帮助的简单的Erlang程序:
注意:
在Python模块py_math_o1中,我们利用支持Python的Erlport调用主函数main。
py_math_01.main返回一个列表,函数show_list/2打印列表的每一项信息。
在Erlang的交互式命令erl中,我们用下面的方式编译和运行:
8.2 Erlang和简单的Erlang/Python进程池
在这个例子中,我们实现了一个Erlang+ Python进程池,能够使我们从进程池请求一个进程(如果必要的话,等待直到一个进程可用),使用它,然后返回到进程池。事实上,进程池中都是Erlang进程,然而,每个Erlang进程都包含(记住PID或者进程的标识)Python进程。我们用Erlport创建每个Python进程。
下面是实现进程池的Erlang代码:
注意:
在使用上面的代码之前,我们先用erlc进行编译。
init/0建立一个ETS表格,使我们记住池中进程的ID。
进程池本身就是一个进程。我们使用它,通过发送消息到标记(得到)和推送(回)一个Python进程。
start/ 3(1)创建Python进程,每一个进程都是由loop/ 2实现的(2)创建一个持有这些Python进程的进程。我们会从第二个进程中请求下一个可用的Python进程,所以我们保存在ETS表它的进程ID。
rpc/1实现了接口或API,能使我们发送请求(远程过程调用)到Python进程和得到返回值。
我们可能会问:为什么pool_loop / 2作为一个进程而不是一个普通的函数来实现。首先要知道的是,一个进程,在Erlang中,仅仅是一个我们重载的函数。然后,接下来,当我们想从一个单独的应用程序(一个单独的操作系统进程)或者从不同的机器上运行的应用程序请求和使用利用Erlang和erlport创建的Python的进程时,在未来,创建和使用进程的实现能力可以给我们一定的灵活性。
下面是一个可以从命令行运行的Erlang脚本,可用于驱动和测试上述Erlang代码:
注意:
我们使用erlopt解析命令行选项和参数。这里是可用的:在Erlang中,erlopt getopt()
我们调用run/5进行初始化和创建进程,然后,调用run_n/5设置一些指定的次数,最后,停止(杀死)我们创建的进程。
run_n/5调用rpc/1,rpc/1使用其中一个创建的进程在Python中利用Numpy进行计算和返回结果。
你可以运行下面例子测试上面的代码:
8.3 一个用来故障恢复的进程池
我们试图在这个例子以及前面的例子获得的是从一个Erlang / Python进程故障恢复的能力。在这段代码中,我们要求,当一个Erlang / Python进程失败,我们收到通知,所以我们可以(1)从进程池中移除旧的(死掉的)进程(2)创建一个新的进程并将其插入到进程池。
下面是实现上述功能的代码:
注意:
请注意,在函数start_python_processes中,我们使用spawn_link而不是spawn创造我们的进程。告诉Erlang(例如,spawn_link进程),当任何一个进程失败时,给我们发送一条消息。
然后,在“start”函数中,我们创建一个监听进程来接收失败信息。监听进程在restarter函数中实现。
函数restarter监听这些消息。如果它收到一条暗示失败的消息,它会给进程池发送一条消息,告诉进程池移除失效进程,创建一个新的进程并添加到进程池中。
在函数pool_loop中,在receive语句后,通过一条新的分句实现了该功能。
下面是驱动实例,一个Erlang脚本,可以用来运行上面的代码:
注意:
我们用erlopt帮助解析命令行参数。这里有可用连接:https://code.google.com/p/erlopt/.
收到命令行选项和参数后,我们调用函数run/5,(1)初始化进程,启动restarter/1;(2)调用Python函数获得请求次数;最后,(3)停止所有的Erlang/Python进程以及restarter/1。
8.4 行为和进程池
我们将前面例子加入Erlang行为以获得弹性的目的。Erlang的行为像模板或框架。该行为提供了样板,和常见的结构、功能;我们提供(仅仅)我们所需要的代码。
我们的“池服务器”创建和管理Erlang/ Python进程池。当客户需要一个进程来运行一个Python函数时,它可以请求一个进程(等待一个可用,如果必要的话),当(客户端)完成Python函数时,将进程归还给进程池。我们试图获得优于以往实例的几个特性:(1)如果(当)进程池中一个Python程序死了,它会被替换;(2)如果池本身(即提供进程池服务的进程)死了,它将重新启动。
在一般意义上,我们将尝试用Erlang实现一个服务器来响应请求(1)启动一批Erlang/ Python进程和拥有这些进程的进程池;(2)从进程池中获得一个Erlang / Python的进程;(3)向进程池返回一个Erlang / Python进程;(4)停止所有的Erlang/Python进程以及进程池服务本身。
下面是我们进程服务器的代码,用以下方式实现:
Erlang supervisor OTP行为
Erlang gen_server OTP行为
poolboy池库,有用链接:https://github.com/devinus/poolboy.git
这是用来启动工作进程池的supervisor模块:
注意:
上述模块是我们的进程监督树的根。它使用的Erlang 的supervisor行为。我们的树很短,只有一层深,上面的监督进程以及它的子工作进程(见下文),都是Erlang/Python进程池的成员。
函数init/1做这些工作。它会从poolboy池得到一个规范的流程,然后当它返回时,请求supervisor模块创建创建这些进程,并把它们添加到我们的监督树中。更多信息见: http://www.erlang.org/doc/man/supervisor.html#Module:init-1
下面是工作模块。它实际上调用了Python进程:
注意:
上面的大部分是框架,服从Erlang的gen_server行为。
当我们的supervisor模块和Erlang的supervisor行为创建了它的子进程,在该模块,它会调用start_link/1 来创建每个子进程,并其相互连接到监督进程。
然后,gen_server 将会调用我们的回调函数 init/1,在该函数中我们利用Erlport创建了Erlang/Python进程。记住,通过Erlport创建的Erlang/Python进程是系统进程,不是Erlang进程。
我们在State中保存Erlang/Python进程(事实上是进程ID)。这是传递给其他回调函数的状态变量;实际上,它给我们提供了一些全局数据。通过这样做,我们有效地把Erlang/ Python进程和Erlang进程连接起来。
另一个使我们感兴趣的回调函数是handle_call/3。它的第一个参数指定我们要它做什么,特别的,它是一个元组,包含三个条目:(1)一个操作(原子请求调用Python函数)(2)包含Python函数(原子)的Python模块,(3)Python函数(原子)。
函数 handle_call/3的功能是利用Erlport调用Python函数,Python函数与Erlang/Python进程相连,并返回回调函数gen_server handle_call/3指定格式的结果。更多信息见:http://www.erlang.org/doc/man/gen_server.html#Module:handle_call-3
下面是向我们演示了怎样使用上面模块的实例:
下面是只显示命令,不显示输出:
9 Node.js和Java
你也可以在Java中用Node.js做一些并行处理。但是,有一些保留意见---Node.js是单线程的。它利用回调函数实现并行机制。因此,你可以并行运行任务,但他们不会利用多核或多CPU。
然而,对于创建多个Node.js进程并运行在多核上,这里有一些支持。了解更多有关支持,点击 https://www.npmjs.com/,在其中搜索“multi core”。
了解更多有关Node.js的信息:https://nodejs.org/
我用Async模块来控制流。Async的许多流控制模式都支持并行执行。你可以利用npm命令安装Async。
关于Async包,点击:https://www.npmjs.com/package/async。
下面是一个可以用Node.js运行的Java脚本:
注意:
除了map, filter, 和parallel(上面的例子使用的),Async 有相当多的额外的功能,与流量控制和集合协助使用。
这些功能的文档在readme.md文件中,该文件还包含了分布功能。更多信息:https://www.npmjs.com/package/async
10 对比
对于在工作规划中你选择上面哪一种技术,这里有一些意见,可能有助于你作出决定:
Python多进程模块(在Python标准库)提供了一个基础,你可以建立你想要的并行处理模型。然而,你不得不建立更多自己的模型。该模块允许你编写Python代码来启动额外的“子”进程,而不是手动的启动,但我想你可以用任何一种符合你需求的语言编写脚本来自动的启动额外的进程。该模块提供了Pipe 和Queue 类来实现各个”子”进程之间以及启动进程之间的通信,但是,不要忘了,你可以使用ZeroMQ实现进程间的通信。Python的multiprocessing 模块可以使你在进程间共享内存,但是,这种方法并不安全。(见:Sharing state between processes)
Parallel Python似乎工作良好。它提供了很大的能力。容易学习。使用简单。它提供了多进程/并行机制和分布式处理。因此,我们可以在同一台机器上使用多核和CPU;也可以在多台机器上运行任务(网络的一个节点)。在代码库本身,似乎没有太多有效的工作。然而,这可能是因为它有用,并没有理由改变它。
IPython并行处理有足够的能力,如果你选择使用它,你可以花很多时间学习如何使用。无论如何,IPython并行处理使我们很多人都想做的常见的并行任务变得很容易甚至很简单。重要的是,你可以在不同的进程之间并行运行相同的任务;你可以在不同的进程之间分配不同的任务。你也可以共享进程之间的同步和异步的方式,我相信,但不确定,是安全的(例如,从运转条件)。
IPython的能力是如此多种多样,很难向你全部概述。阅读IPython概述文档,然后寻找你所需要的能力。当然,如果你需要,你可以使用ZeroMQ处理进程间通信。事实上,ZeroMQ是用IPython本身实现的。为了一定目的,认为IPython并行处理建立在ZeroMQ上是有道理的,它缺少了一些的灵活性,使我们能够做一些用ZeroMQ可以做的事情(ZeroMQ给予了足够的努力和智慧),但用IPython的方式,是非常容易和简单的。
英文原文:http://www.davekuhlman.org/python_multiprocessing_01.html
责任编辑: