看過《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()
請看個人另外一篇公衆號:
返回的result對象的.get()
方法被調用了。可是因爲MapResult
自己沒有.get()
方法,因而變爲調用父類ApplyResult
的.get()
方法。
再進入ApplyResult
裏面,查看.get()
方法:
因爲前面調用了self._event.set()
,因此這裏的self.ready()
結果爲True
,而因爲self._success
在上面爲True
,因此這裏直接return self._value
。也就是返回一個空的列表。
到此爲止,在pool.map
的第二個參數爲空的可迭代對象時,全部的流程就走完了。整個過程當中,沒有涉及到任何調用func
的過程。因此原有的函數不會被執行。
最後說說爲何在本文中咱們看的是multiprocessing
的Pool
類裏面的map
方法,而不是multiprocessing.dummy
的Pool
類裏面的map
方法。
這是由於,若是咱們打開Python安裝路徑/Lib/multiprocessing/dummy/__init__.py
,咱們就能夠看到,它的Pool
實際上返回的是一個ThreadPool
對象。而這個對象的代碼,實際上也在Python安裝路徑/Lib/multiprocessing/pool.py
文件中,而且繼承自Pool
類。因此他們的map
方法的代碼是徹底同樣的。