从Python源代码里面证实你的猜测

看过《Python爬虫开发 从入门到实战》的同窗,应该对multiprocessing这个模块比较熟悉,在书上我使用这个模块经过几行代码实现了一个简单的多线程爬虫:python

import requests
from multiprocessing.dummy import Pool

def get(url):
    print(requests.get(url).text, '\n')

url_list = [
    'http://exercise.kingname.info/exercise_middleware_ip/1',
    'http://exercise.kingname.info/exercise_middleware_ip/2',
    'http://exercise.kingname.info/exercise_middleware_ip/3',
    'http://exercise.kingname.info/exercise_middleware_ip/4'
]

pool = Pool(3)
result = pool.map(get, url_list)
复制代码

运行效果以下图所示:多线程

(没有看过个人书的人可能会质疑,multiprocessing不是多进程模块吗?为何你说是多线程?看过书的读者不会有这个疑惑,由于我在书上解释过缘由)async

如今,你有一个函数,没有任何参数,可是仍然想让他使用多线程,因而模仿上面的代码,你这样写:函数

import requests
from multiprocessing.dummy import Pool

def test():
    print('函数运行成功!')


pool = Pool(3)
result = pool.map(test, ())
复制代码

运行之后发现,什么都没有打印出来,也就是说test()函数根本没有运行。url

若是你强行给函数添加一个没用的参数,结果又正常了:spa

import requests
from multiprocessing.dummy import Pool

def test(_):
    print('函数运行成功!\n')


pool = Pool(3)
result = pool.map(test, (0, ) * 3)
复制代码

运行效果以下图所示。 线程

因此你隐隐以为,若是pool.map的第二个参数是空的可迭代对象,那么函数就不会运行。3d

(固然,使用过Python自带的map函数的同窗确定直接就知道这一点,不过本文依然使用它来作例子,用于说明阅读源代码的方法。)code

为了证实这一点,咱们打开Python安装目录/lib/multiprocessing/pool.py文件,在里面找到def map(self, func, iterable, chunksize=None)这一行,以下图所示:cdn

(本文使用Python 3.7.3做为演示,若是你的Python版本不是3.7.3,那么代码可能会有一些区别)

从代码里面能够看到,这里调用了self._map_async(),传入参数,得到返回值之后,再调用了返回值的.get()方法。

因此继续看self._map_async()方法:

在这个方法里面,若是咱们传入的可迭代对象为空,那么也就是这里的参数iterable为空。因而

chunksize = 0
len(iterable) = 0
复制代码

map的第一个参数,函数名被传入了下面这一行代码中:

task_batches = Pool._get_tasks(func, iterable, chunksize)
复制代码

查看Pool._get_tasks这个静态方法,能够看到:

因为这里的参数it就是空的可迭代对象,size为0,因此下面这一行代码返回空元组:

tuple(itertools.islice(it, size))
复制代码

这个生成器直接就会结束,最后一行yield (func, x)根本不会执行。

再来看代码里使用MapResult类初始化了一个result对象,而后返回这个对象。

再进入到MapResult类里面,以下图所示:

在这段__init__中,能够获得以下几个参数的值:

self._success = True
self._value = []  # 由于[None] * 0 结果为[]
self._event.set()
复制代码

关于self._event.set()请看个人另外一篇公众号:

一日一技:Python多线程的事件监控

返回的result对象的.get()方法被调用了。可是因为MapResult自己没有.get()方法,因而变为调用父类ApplyResult.get()方法。

再进入ApplyResult里面,查看.get()方法:

因为前面调用了self._event.set(),因此这里的self.ready()结果为True,而因为self._success在上面为True,因此这里直接return self._value。也就是返回一个空的列表。

到此为止,在pool.map的第二个参数为空的可迭代对象时,全部的流程就走完了。整个过程当中,没有涉及到任何调用func的过程。因此原有的函数不会被执行。

最后说说为何在本文中咱们看的是multiprocessingPool类里面的map方法,而不是multiprocessing.dummyPool类里面的map方法。

这是由于,若是咱们打开Python安装路径/Lib/multiprocessing/dummy/__init__.py,咱们就能够看到,它的Pool实际上返回的是一个ThreadPool对象。而这个对象的代码,实际上也在Python安装路径/Lib/multiprocessing/pool.py文件中,而且继承自Pool类。因此他们的map方法的代码是彻底同样的。

相关文章
相关标签/搜索