爬虫的并发控制:python
多进程、多线程、协程 yield程序员
从硬件:redis
双核四线程(超线程技术):
有两个CPU核心,每一个核心有两个逻辑处理器,至关于有四个CPU核心数据库
四核四线程:
有一个CPU核心,每一个核心有一个逻辑处理器,至关于有四个CPU核心设计模式
从操做系统:缓存
进程和线程,都是CPU任务的执行单位。安全
进程:早期的操做系统是面向进程的:
表示一个程序的执行活动(打开、执行、保存、关闭)网络
线程:如今的操做系统都是面向线程:
表示一个进程处理任务时最小调度单位(执行功能a、执行功能b)多线程
一个程序至少开启一个进程,一个进程至少有一个线程。并发
每一个进程都有独立的内存空间,不一样进程之间不共享任何状态。
进程之间的通讯须要通过操做系统调度控制,通信效率低、切换开销大。
同一个进程里的多个线程,是共享内存空间,切换开销小,通信效率高。
线程的工做机制是"抢占式",出现竞争的状态,竞争意味着数据不安全。
引入了"互斥锁":让多个线程安全有序的访问内存空间的机制。
Python的多线程:
相似于 GIL(全局解释器锁):保证一个时间片里只有一个线程在运行。
好处:直接杜绝了多个线程的竞争问题:
坏处:Python的多线程不是真正的多线程。
Python解释器在处理IO阻塞类型的方法时,会释放GIL
若是没有IO操做,该线程会每隔 sys.getcheckinterval() 次释放GIL,让其余线程尝试执行。
并行:
同一CPU时间片内,CPU能够同时处理多个程序。若是有多个程序,同步执行。
程序1:----------------
程序2:----------------
程序3:----------------
程序4:----------------
并发:
同一CPU时间片,只能处理一个程序。若是有多个程序,交替执行。
程序1: ----- ------
程序2: -----
程序3: ----
程序4: -----
多进程:能够充分利用多核CPU的资源,适用于密集CPU任务(大量的并行运算)
Python的多进程模块:multiprocessing
进程之间通讯成本高切换开销大,不适用于须要大量数据通讯和切换的任务(爬虫)
设计模式:生产者消费者(并行模式)
多线程:适用于密集I/O任务(磁盘IO,内存IO,网络IO),切换开销小通讯成本低。
Python的多线程:Thread、thraeding、multiprocessing.dummy
多线程:同一个时间片只能执行一个线程,没法充分利用CPU多核资源(只能作到并发,不能作到并行)
协程:操做系统和CPU不认识协程,是由程序员经过代码逻辑控制。
特色是在单线程下执行多个任务,且不须要经过操做系统切换(没有切换开销,也不须要处理锁),执行效率高。
Python: gevent ,猴子补丁(Python代码在执行网络IO阻塞时,会自动切换协程)
协程:适用于密集网络I/O任务
多进程爬虫:不合适
多线程爬虫:缺点 - 经过操做系统调度,有线程切换开销(海量URLs场景会增长CPU负载);优势 - 使用场景普遍(网络读写并发/数据库读写并发/磁盘读写并发)
协程爬虫:缺点 - gevent配合monkey.patch_all() 只能提升网络并发效率,不能处理其余并发场景;优势 - 经过程序员代码逻辑控制,不受操做系统调度,没有切换开销,下降CPU负载(处理海量URLs优点明显)
执行方式:
同步:执行一个任务,必须等待上一个任务完成(没有并发的爬虫)
异步:执行一个任务,没必要等待上一个任务完成(并发的爬虫)
程序状态:
阻塞:程序执行时,必须等待该任务完成,不然保持等待状态。
非阻塞:程序执行时,没必要等待该任务完成,能够继续执行下一个任务。
异步+非阻塞(效率最高):
发送请求后,没必要等待响应返回,能够继续处理其余请求发送;当处理功能挂起时,可以马上切换其余到功能继续执行。
异步网络框架 Twisted Tornada
Scrapy :请求处理模块+响应解析模块+twisted
scrapy-redis:Scrapy + Redis(在同一个数据库里处理请求去重、请求分配、数据存储)
单机爬虫:
分布式爬虫:
CPU -> 寄存器 -> CPU缓存L1/L2/L3 -> 内存 -> 硬盘/固态硬盘 -> 网络
协程:切换
三种方式实现: 1. yield 2.greenlet 3. gevent
1.yield
做用:
挂起当前函数 将后面表达式的值 返回到调用生成器的地方
接收数据 并唤醒当前函数 而且紧接着上次运行的地址继续执行
2.greenler
greenlet(函数) 建立协程
gr2.switch()切换到gr2执行
协程是python个中另一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为须要的资源)
通俗的理解:在一个线程中的某个函数,能够在任何地方保存当前函数的一些临时变量等信息,而后切换到另一个函数中执行,注意不是经过调用函数的方式作到的,而且切换的次数以及何时再切换到原来的函数都由开发者本身肯定
建立并执行协程
阻塞等待协程运行完成 .join()
阻塞等待全部协程退出 .join_all()
monkey.patch_all()做用是将rece, recefrom, time.sleep, accept进行破解, 不会阻塞等待, 在调用时能够切换到别的任务继续执行.
注意:join,join_all做用保持主进程存活
简单总结