添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

image

摄影: 产品经理

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

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)

运行效果如下图所示:

image

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

现在,你有一个函数,没有任何参数,但是仍然想让他使用多线程,于是模仿上面的代码,你这样写:

import requests
from multiprocessing.dummy import Pool
def test():
    print('函数运行成功!')
pool = Pool(3)
result = pool.map(test, ())

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

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

import requests
from multiprocessing.dummy import Pool
def test(_):
    print('函数运行成功!\n')
pool = Pool(3)
result = pool.map(test, (0, ) * 3)




    

运行效果如下图所示

image

所以你隐隐觉得,如果 pool.map 的第二个参数是空的可迭代对象,那么函数就不会运行。

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

为了证明这一点,我们打开 Python安装目录/Lib/multiprocessing/pool.py 文件,在里面找到 defmap(self,func,iterable,chunksize=None) 这一行,如下图所示:

image

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

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

所以继续看 self._map_async() 方法:

image

在这个方法里面,如果我们传入的可迭代对象为空,那么也就是这里的参数 iterable 为空。于是

chunksize = 0
len(iterable) = 0

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

  1. task_batches = Pool._get_tasks(func, iterable, chunksize)

查看 Pool._get_tasks 这个静态方法,可以看到:

image

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

tuple(itertools.islice(it, size))

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

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

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

image

在这段 __init__ 中,可以得到如下几个参数的值:

self._success = True
self._value = []  # 因为[None] * 0 结果为[]
self._event.set()

关于 self._event.set() 请看我的另一篇公众号:

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

返回的result对象的 .get() 方法被调用了。但是由于 MapResult 本身没有 .get() 方法,于是变为调用父类 ApplyResult .get() 方法。

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

image

由于前面调用了 self._event.set() ,所以这里的 self.ready() 结果为 True ,而由于 self._success 在上面为 True ,所以这里直接 returnself._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 方法的代码是完全一样的。


(肝了三个小时的困难题-必须记录一下)剑指 Offer 37. 序列化二叉树-----python && C++源代码
(肝了三个小时的困难题-必须记录一下)剑指 Offer 37. 序列化二叉树-----python && C++源代码
字节跳动笔试题——复杂链表的复杂——剑指 Offer 35. 复杂链表的复制——python && C++源代码
字节跳动笔试题——复杂链表的复杂——剑指 Offer 35. 复杂链表的复制——python && C++源代码
压入弹出堆栈算法-附LeetCode剑指 Offer 31. 栈的压入、弹出序列-题解-python && C++源代码
压入弹出堆栈算法-附LeetCode剑指 Offer 31. 栈的压入、弹出序列-题解-python && C++源代码
经典位运算算法模板-附LeetCode剑指 Offer 56 - I. 数组中数字出现的次数-题解-python && C++源代码
经典位运算算法模板-附LeetCode剑指 Offer 56 - I. 数组中数字出现的次数-题解-python && C++源代码
双指针滑窗经典问题算法模板-附LeetCode每日一题题解:713. 乘积小于 K 的子数组-题解-python && C++源代码
双指针滑窗经典问题算法模板-附LeetCode每日一题题解:713. 乘积小于 K 的子数组-题解-python && C++源代码
queue队列算法模板-附LeetCode每日一题题解:1823. 找出游戏的获胜者-题解-python && C++源代码
queue队列算法模板-附LeetCode每日一题题解:1823. 找出游戏的获胜者-题解-python && C++源代码
LeetCode每日一题题解:2024. 考试的最大困扰度-题解-python && C++源代码
LeetCode每日一题题解:2024. 考试的最大困扰度-题解-python && C++源代码
LeetCode每日一题题解:693. 交替位二进制数-题解-python && C++源代码-经典位运算
LeetCode每日一题题解:693. 交替位二进制数-题解-python && C++源代码-经典位运算
LeetCode每日一题题解:912. 排序数组-题解-python && C++源代码-快速排序代码模板
LeetCode每日一题题解:912. 排序数组-题解-python && C++源代码-快速排序代码模板