從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方法的代碼是徹底同樣的。

相關文章
相關標籤/搜索