之前文章对python中进程池的原理、数据流以及应用从代码角度做了简单的剖析,现在让我们回头看看标准库中对进程池的实现都有哪些值得我们学习的地方。我们知道,进程池内部由多个线程互相协作,向客户端提供可靠的服务,那么这些线程之间是怎样做到数据共享与同步的呢?在客户端使用apply/map函数向进程池分配任务时,使用self._taskqueue来存放任务元素,_taskqueue定义为Queue.Queue(),这是一个python标准库中的线程安全的同步队列,它保证通知时刻只有一个线程向队列添加或从队列获取元素。这样,主线程向进程池中分配任务(taskqueue.put),进程池中_handle_tasks线程读取_taskqueue队列中的元素,两个线程同时操作taskqueue,互不影响。进程池中有N个worker进程在等待任务下发,那么进程池中的_handle_tasks线程读取出任务后,又如何保证一个任务不被多个worker进程获取到呢?我们来看下_handle_tasks线程将任务读取出来之后如何交给worker进程的:

for taskseq, set_length in iter(taskqueue.get, None):
i = -
for i, task in enumerate(taskseq):
if thread._state:
debug('task handler found thread._state != RUN')
break
try:
put(task)
except Exception as e:
job, ind = task[:]
try:
cache[job]._set(ind, (False, e))
except KeyError:
pass
else:
if set_length:
debug('doing set_length()')
set_length(i+)
continue
break
else:
debug('task handler got sentinel')
在从taskqueue中get到任务之后,对任务中的每个task,调用了put函数,这个put函数实际上是将task放入了管道,而主进程与worker进程的交互,正是通过管道来完成的。
再来看看worker进程的定义:
w = self.Process(target=worker,
args=(self._inqueue, self._outqueue,
self._initializer,
self._initargs, self._maxtasksperchild)
)
其中self._inqueue和self._outqueue为SimpleQueue()对象,实际是带锁的管道,上述_handle_task线程调用的put函数,即为SimpleQueue对象的方法。我们看到,这里worker进程定义均相同,所以进程池中的worker进程共享self._inqueue和self._outqueue对象,那么当一个task元素被put到共享的_inqueue管道中时,如何确保只有一个worker获取到呢,答案同样是加锁,在SimpleQueue()类的定义中,put以及get方法都带有锁,进行同步,唯一不同的是,这里的锁是用于进程间同步的。这样就保证了多个worker之间能够确保任务的同步。与分配任务类似,在worker进程运行完之后,会将结果put会_outqueue,_outqueue同样是SimpleQueue类对象,可以在多个进程之间进行互斥。

  在worker进程运行结束之后,会将执行结果通过管道传回,进程池中有_handle_result线程来负责接收result,取出之后,通过调用_set方法将结果写回ApplyResult/MapResult对象,客户端可以通过get方法取出结果,这里通过使用条件变量进行同步,当_set函数执行之后,通过条件变量唤醒阻塞在get函数的主进程。

  进程池终止工作通过调用Pool.terminate()来实现,这里的实现很巧妙,用了一个可调用对象,将终止Pool时的需要执行的回调函数先注册好,等到需要终止时,直接调用对象即可。

self._terminate = Finalize(
    self, self._terminate_pool,
    args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
self._worker_handler, self._task_handler,
self._result_handler, self._cache),
    exitpriority=15
)
在Finalize类的实现了__call__方法,在运行self._terminate()时,就会调用构造self._terminate时传入的self._terminate_pool对象。

  使用map/map_async函数向进程池中批量分配任务时,使用了生成器表达式:

self._taskqueue.put((((result._job, i, mapstar, (x,), {}) for i, x in enumerate(task_batches)), None))
生成器表达式很简单,只需把列表解析的的[]换成()即可,上述表达的列表解析表示为:
[(result._job, i, mapstar, (x,), {}) for i, x in enumerate(task_batches)]
这里使用生成器表达式的好处是,它相当于列表解析的扩展,是对内存有好的,因为它只是生成了一个生成器,当我们需要使用该生成器对应的逻辑目标数据时,它才会通过既定逻辑去生成该数据,所以不会大量占用内存。

  在Pool中,_worker_handler线程负责监控、创建新的工作进程,在监控工作进程退出时,同时将退出的进程从进程池中删除掉。这类似于,一边遍历一边删除列表。我们来看下下面代码的实现:

>>> l = [1, 2, 3, 3, 4, 4, 4, 5]
>>> for i in l:
if i in [3, 4, 5]:
l.remove(i) >>> l
[1, 2, 3, 4, 5]

我们看到l没有将所有的3和4都删除掉,这是因为remove改变了l的大小。再看下面的实现:

>>> l = [1, 2, 3, 3, 4, 4, 4, 5]
>>> for i in range(len(l)):
if l[i] in [3, 4]:
del l[i] Traceback (most recent call last):
File "<pyshell#37>", line 2, in <module>
if l[i] in [3, 4]:
IndexError: list index out of range
>>>

同样因为del l[i]时,l的大小改变,继续访问下去导致访问越界。而标准库中的进程池给出了遍历删除的一个正确示例:

for i in reversed(range(len(self._pool))):
worker = self._pool[i]
if worker.exitcode is not None:
worker.join()
cleaned = True
del self._pool[i]

使用reversed,从后向前删除list中的元素,这样会保证所有符合删除条件的元素被删除掉:

>>> l = [1, 2, 3, 3, 4, 4, 4, 5]
>>> for i in reversed(range(len(l))):
if l[i] in [3, 4, 5]:
del l[i] >>> l
[1, 2]

  可以看出,一个篇幅并不算大的Pool模块,就有很多值得学习的地方。对于python亦或者其他语言,技能的提升,多阅读标准库中代码,是一个很不错的选择。对于我们经常使用,而不知其中实现奥秘的源码,多阅读源码,了解其技术实现,就像侯捷那本《STL源码剖析》中讲到的,源码之前,了无秘密。更重要的是,将这些漂亮而又高效的编码方式,运用在自己的工作中,让自己的代码也可以像标准库中的代码一样优雅,这可以说是每一个开发人员的追求。

python进程池剖析(三)的更多相关文章

  1. python进程池剖析(一)

    python中两个常用来处理进程的模块分别是subprocess和multiprocessing,其中subprocess通常用于执行外部程序,比如一些第三方应用程序,而不是Python程序.如果需要 ...

  2. python进程池剖析(二)

    之前文章中介绍了python中multiprocessing模块中自带的进程池Pool,并对进程池中的数据结构和各个线程之间的合作关系进行了简单分析,这节来看下客户端如何对向进程池分配任务,并获取结果 ...

  3. Python进程池multiprocessing.Pool的用法

    一.multiprocessing模块 multiprocessing模块提供了一个Process类来代表一个进程对象,multiprocessing模块像线程一样管理进程,这个是multiproce ...

  4. python进程池:multiprocessing.pool

    本文转至http://www.cnblogs.com/kaituorensheng/p/4465768.html,在其基础上进行了一些小小改动. 在利用Python进行系统管理的时候,特别是同时操作多 ...

  5. python(进程池/线程池)

    进程池 import multiprocessing import time def do_calculation(data): print(multiprocessing.current_proce ...

  6. 万里长征第一步:Python进程池的一点点小坑

    # -*- coding: utf- -*- """ Created on Thu Mar :: @author: lilide """ # ...

  7. python进程池

    当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiproce ...

  8. python 进程池的简单使用方法

    回到python,用一下python的进程池. 记得之前面试的时候,面试官问:你知道进程池的默认参数吗? 我没有回答上来,后来才知道,是有默认参数的.下面就看看它的默认参数 1. 不加参数 from ...

  9. python 进程池的使用和坑

    from multiprocessing import Pool,Process import time,os def Foo(a):#创建函数 time.sleep(2) print('in the ...

随机推荐

  1. android在当前app该文件下创建一个文件夹

    /*********************************************************************  * Author  : Samson  * Date   ...

  2. Oracle 多行转多列

    Oracle 多行转多列,列值转为列名   前段时间做调查问卷,客户创建自定义问卷内容,包括题目和选项内容; 之后需要到处问卷明细,,,,麻烦来咯 于是到网上到处搜索,没有直接结果;于是又找各种相似的 ...

  3. Dubbo与Zookeeper、SpringMVC整合和利用(负载均衡、容错)

    互联网发展,扩大了网站应用程序的大小.传统的垂直应用架构已经无法应付.分布式服务架构和流量计算架构势在必行,Dubbo是一个分布式服务框架.在这样的情况下诞生的.如今核心业务抽取出来.作为独立的服务, ...

  4. Android-采用Matrix对Bitmap加工

    1.Android正在使用Matrix放.旋转.平移.斜切等变换的. Matrix是一个3*3的矩阵,其值相应例如以下: 以下给出详细坐标相应变形的属性|scaleX, skewX, translat ...

  5. android 环境使用smack 必须注冊的组件

    // Private Data Storage pm.addIQProvider("query", "jabber:iq:private",new Privat ...

  6. Android Intent机制与常见的用法

    Activity Android于.Activity所有的程序都是必不可少,程都执行在Activity之中.Activity具有自己的生命周期(见http://www.cnblogs.com/feis ...

  7. c#中 ==与equals有什么区别

    对于值类型.引用类型来说比较过程怎样的? using System;using System.Collections.Generic;using System.Text; namespace Cons ...

  8. 房间计费系统改造E-R图纸设计

    简单的学习过程:     这几天忙得太混乱了,用了近一个星期才设计好.我在这段时间遇到的困难,就积极找师哥师姐指点迷津,如今多少总算是有些拿得出手的成果. 学习成果: Entity Relations ...

  9. angularJS之使用指令封装DOM操作

    angularJS之使用指令封装DOM操作 创建指令 指令也是一种服务,只是这种服务的定义有几个特殊要求: 必须使用模块的directive()方法注册服务 必须以对象工厂/factory()方法定义 ...

  10. hdu 5053 the Sum of Cube(上海网络赛)

    the Sum of Cube Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...