Python3爬虫系列02 (理论) - Python并发编程

  • 原创
  • Madman
  • /
  • 2018-08-02 15:03
  • /
  • 0
  • 278 次阅读

spider 02-min.jpg

Synopsis: 本文是整个爬虫系列的理论基础,试想一下,如果你的爬虫只能一次下载一张图片,那要爬完整个图片网站的时间会让人抓狂,所以我们需要让程序能够并发,同时请求多张图片资源,因为网络传输时间对于CPU来说太漫长了,并发的好处是可以合理的解决CPU和网络I/O之间的速度鸿沟

代码已上传到 https://github.com/wangy8961/python3-concurrency ,欢迎star

1. 并发编程

1.1 为什么需要并发

device CPU cycles Proportional "human" scale
L1 cache 3 3 seconds
L2 cache 14 14 seconds
RAM 250 250 seconds
disk 41,000,000 1.3 years
network 240,000,000 7.6 years

假设CPU读取L1缓存要用3秒,那么读取网络I/O要用7.6年!CPU的速度远快于磁盘I/O或网络I/O,如果使用同步阻塞的方式去请求1000张图片,在只有一个进程的情况下,一旦遇到I/O操作(请求第1张图片数据),当前进程被挂起,直到I/O操作完成,才能继续请求第2张图片

1.2 并发与并行的区别

concurrency-parallelism

想象一下,你正在做饭和写小说,看起来你好像在同时做这两件事,但你不过是在这两件事之间不断切换而已,当你在等待水煮沸的时候你在写小说,当你要处理蔬菜等食材时你会暂停写小说,这种方式称为并发,CPU会快速的切换执行多个任务,由于CPU的时钟周期对于人眼来说太快了,以至于你会感觉好像多个任务是同时在执行一样,就好像你可以在电脑上看电影同时与朋友网络聊天。要实现并行,必须有多核CPU。就好像有两个人,一个烹饪,另一个同时写小说

1.3 并发编程的方式

Python中如何实现并发编程(concurrent programming),即 Do Multiple Things At Once:

  • 多进程
  • 多线程
  • 异步编程(asynchronous programming)

2. Processes vs. Threads vs. Async

Processes Threads Async
Optimize waiting periods Yes (preemptive) Yes (preemptive) No (cooperative)
Use all CPU cores Yes No No
Scalability Low (ones/tens) Medium (hundreds) High (thousands+)
Use blocking standard library functions Yes Yes No
GIL interference No Some No

2.1 Multiple Processes

  • 抢占式多任务(preemptive),由操作系统调度。The OS does all the multi-tasking work
  • 多进程是三种并发模式中唯一可以使用多核CPU的模式。Only option for multi-core concurrency

2.2 Multiple Threads

  • 抢占式多任务(preemptive),由操作系统调度。The OS does all the multi-tasking work
  • Python默认解释器CPython由于GIL的存在,不能使用多核CPU,只能运行在一个核心上。In CPython, the GIL prevents multi-core concurrency

2.3 Asynchronous Programming

  • 协作式多任务(cooperative),由用户自己决定在程序中切换执行哪一段代码。No OS intervention
  • 单进程、单线程。One process, one thread

异步编程是如何实现多任务的呢?举个例子,有一天国际象棋天才,她同时与24个普通选手对弈。假设天才每走一步棋是5秒,普通选手每走一步棋是55秒,同时每个局棋平均需要30个来回分出胜负(天才和普通选手各走1步是一个来回),假设天才在各棋局之间走动的时间忽略不计

  • 同步对弈 Synchronous

天才与第1个普通选手对弈完成后,才开始与第2个普通选手对弈,依次类推。每个棋局用时: 30 * (5 + 55 秒) = 30 分钟,24个对手,就是24个棋局,总用时: 24 * 30 分钟 = 12 小时

  • 异步对弈 Asychronous

天才先开始第1个棋局(与普通选手1对弈),天才用了5秒走了第1步棋,然后,她的对手要花费55秒才能下第1步。这个时间对天才来说太久了,所以,在第1个普通选手思考的期间,天才走向第2个棋局,又用5秒走了第1步棋,接着走向第3个棋局,依次类推,天才下完每个棋局的第1步要花费: 24 * 5 秒 = 2 分钟

此时,120秒过去了,第1个棋局的普通选手早就走出了第1步。当天才又来到他的面前时,天才用了5秒,走了第2步,而普通选手又要思考怎么走。所以,天才又去第2个棋局花费5秒下了第2步,依次类推。

天才走完30轮总用时: 30 * 2 分钟 = 1 小时,比同步对弈提高了12倍!

异步编程的详细定义:

异步编程是指在单线程中并发执行多个任务,当一个任务在等待数据时,它会释放CPU资源,转而执行其它任务,通过程序员自己主动切换任务来最小化空闲时间。A style of concurrent programming in which tasks release the CPU during waiting periods, so that other tasks can use it.

3. 如何实现异步?

3.1 Suspend and Resume

  • 异步函数能够被暂停(suspend)恢复执行(resume)。async functions need the ability to suspend and resume
  • 异步函数在等待数据时被暂停执行,当数据到达时,又能够在被暂停的位置处恢复执行。a function that enters a waiting period is suspended, and only resumed when the wait is over

3.2 Implement suspend/resume in Python

  • 回调函数 - Callback functions
  • 基于生成器的协程函数 - generator functions(Python 3.4)
  • 原生协程 - native coroutine,使用 async / await 关键字(Python 3.5+)
  • greenlet (gevent, Eventlet

3.3 Scheduling Asychronous Tasks

  • 异步框架需要实现一个调度器,通常叫事件循环(event loop)。async frameworks need a scheduler, usually called "event loop"
  • 由事件循环跟踪所有正在运行的任务。the loop keeps track of all the running tasks
  • 当一个异步函数被暂停时,控制权将返回给事件循环,然后由事件循环启动或恢复另一个异步函数。when a funtion is suspended, return controls to the loop, which then finds another funtions to start or resume
  • 这种方式叫做协作式多任务。this is called "cooperative multi-tasking"

4. 异步I/O操作

多进程或多线程方案中,操作系统不能无上限地增加进程或线程,一方面会占用大量内存,影响系统稳定性;另一方面上下文切换的开销也很大,一旦进程或线程的数量过多时,CPU的大部分时间就花在上下文切换上了,真正运行代码的时间就少了,结果是导致性能严重下降

而异步I/O框架中,使用单线程,利用事件循环,不断地重复 "监控到事件发生 -- 处理事件" 这一过程。同时,把每个阻塞型操作(blocking operation )转换成非阻塞的异步调用(non-blocking asynchronous call),当某个任务中遇到耗时的I/O操作时,会把控制权交还事件循环,然后事件循环会执行另一个任务。这样就可以避免阻塞型调用中止整个应用程序的进程,合理地解决了CPU高速执行能力和I/O设备的龟速严重不匹配问题

那么 "请求的网络I/O数据已到达" 这类事件怎么通知给事件循环呢?这就要用到I/O多路复用(I/O multiplexing)模型,也叫event driven I/O,不同的操作系统提供了不同的I/O事件通知接口,比如最早的selectpoll,后来BSD中实现了kqueque,Linux则在2.5之后实现了epoll接口,详情见我的博客 Python3爬虫系列01 (理论) - I/O Models 阻塞 非阻塞 同步 异步。Python中select模块提供Low-level I/O multiplexing,而Python 3.4开始新增的selectors模块提供了High-level and efficient I/O multiplexing

Python3爬虫系列08 (理论) - 使用asyncio模块实现并发将要介绍的异步I/O框架asyncio中的事件循环就是基于selectors模块,它会根据不同的操作系统平台使用不同的事件循环对象,比如asyncio.SelectorEventLoopasyncio.ProactorEventLoop(Proactor只能用于Windows系统,使用IOCP),事件循环对象会根据操作系统自动选择最优的I/O multiplexing接口,比如在Linux中会自动使用epoll

异步I/O操作是指,你发起一个I/O操作(比如,等待网络图片数据的到来),却不用等它结束,你可以继续去做其它的事情,当它结束时,你会得到通知,然后再回来接着处理这个I/O后续的操作。而同步I/O操作则会被阻塞在I/O操作上直到它完成,这期间CPU做了很多事,只是没有运行你的程序

注意: Python标准库中的 阻塞型 函数不支持异步,比如 time.sleep()urllib.request.urlopen() 等会阻塞整个程序,并不会把控制权交还给事件循环。异步框架中需要实现对应的 非阻塞型 的异步操作函数,如 asyncio.sleep()aiohttp.request()

代码已上传到 https://github.com/wangy8961/python3-concurrency ,欢迎star

未经允许不得转载: LIFE & SHARE - 王颜公子 » Python3爬虫系列02 (理论) - Python并发编程

分享

作者

作者头像

Madman

如果博文内容有误或其它任何问题,欢迎留言评论,我会尽快回复; 或者通过QQ、微信等联系我

0 条评论

暂时还没有评论.

发表评论前请先登录