逆向爬虫08 并发异步编程

我将引用我最喜欢的路飞学城林海峰Egon老师的讲课方式来复习这一节的内容。

1. 什么是并发异步编程?

这一部分的内容来源于计算机操作系统,如果想要详细了解并发异步等概念,最好去学一下操作系统这门课程,这里只是复习,总结和分享一下我所学到的内容。

要解释清楚并发异步编程,需要先弄明白如下这些词语的意思,如:并行,并发,同步,异步,阻塞,非阻塞。

并行:多个任务同时进行,可以理解为鸣人学习螺旋丸,多重影分身之后,所有的分身同时一起搓丸子,即真正意义上的同时进行。

并发:多个任务快速轮流切换运行,可以理解为时间管理大师,同时交往多个异性,但时间上并不冲突,通过快速切换来实现宏观意义上的同时进行。

同步:传统的任务都是同步的,意思就是所有任务整齐划一地排好队,一个挨着一个进入到CPU运行,只有上一个任务完成后,才能执行下一个任务。

异步:与同步相对,多个任务可以同时进行,异步的方式可以是并行,也可以是并发,只要宏观意义上是同时进行,就属于异步。

阻塞:当需要执行一个包含IO操作的任务时,操作系统会阻塞该任务,等IO操作完成后,再来继续执行该任务,阻塞的意思就是操作系统剥夺该任务的CPU使用权,将CPU分配给其他任务。

非阻塞:与阻塞相对,操作系统不会剥夺该任务的CPU使用权,如果执行IO操作时,IO已经准备好,则非阻塞任务执行成功,如果IO没有准备好,则直接告诉调用方执行失败。

知道了这些概念之后,应该就知道什么是并发异步编程了吧?指的就是微观上地多个任务快速切换,宏观上多个任务同时进行的一种编程方式,可以让程序的运行效率提高N倍。扯了这么多概念,对于非科班的小白来说,应该还是很难快速接受和掌握,下面我就再祭出经典的冯诺依曼体系结构图来说明一下,何为并发异步编程(其实我也是非科班的野路子,嘿嘿)。

上图就是冯诺依曼体系结构图,它告诉我们计算机分为五个部分,运算器,控制器,存储器,输入设备,输出设备。传统的同步编程中,当程序进入到IO操作时,操作系统为了提高硬件的使用效率,会阻塞进入IO的程序,将CPU分配给其他就绪的程序。任何一个程序,都需要通过输入设备,将数据输入到存储器中,由运算器来计算这些输入的数据,最后再通过输出设备输出计算结果,整个过程由控制器控制。通过上图可知,IO操作由输入和输出设备完成,计算由运算器完成,因此理论上这三个部分是可以同时工作的,有些程序过于简单,三个部分只能挨个进行。但如果一个程序中包含多个任务,且每个任务都包含一定的IO和计算操作,则可以通过合理地分配三者,达到同时运行的效果,最终从宏观上表现为好多好多任务同时进行的样子,这个合理分配的操作,也就是并发异步编程。

其实除了上述概念,如进程,线程,协程,这些概念其实都已经被人讲烂了,比如进程是操作系统资源分配的最小单位;线程是操作系统任务调度的最小单位;协程是单线程下,不涉及操作系统,通过软件自行调度的任务,由于不涉及到操作系统,因此速度比线程调度更快。这里我通过实验,来加强对进程概念的理解。平时我们都听说过2核4线程,4核8线程的CPU,CPU的核数表示可真正意义并行运行的任务数量。一般我们写的python程序只有一个进程,这个进程会被分配一个CPU的核,这也就是进程是资源分配的最小单位。我的电脑是8核的,因此每个核占12.5%的CPU效能,平时通过任务管理器查看CPU的使用率是1%,当我写一个python程序进行死循环做计算时,由于没有IO操作,操作系统就会分配一个核来全力支持它,不进行阻塞和切换,此时再观察任务管理器,就可以看到1%+12.5%≈14%左右。因此如果我们再开7个子进程,来执行相同计算操作的话,系统就会额外再分配7个核来支持,8核CPU就被分配完了,电脑会十分的卡顿,大家可以自行做这个实验,来对CPU和进程有更多的了解。关于线程,协程的实验,可以自行探索,如果发现合适且有趣的实验可以告诉我。

i = 0
while True:i += 1

2. 为什么要并发异步编程,什么时候适合并发编程?

那还用说,当然是速度快啊,那什么情况下适合通过并发异步编程来提高程序速度呢?通过前面的描述可知,并发异步编程就是合理分配不同的任务给输入输出设备和运算器,使这些设备可以分别为运行在不同阶段的任务服务,三者同时为一个程序进行服务,该程序的效率就会被提升。因此当一个程序的任务中包含很多离散的IO和计算操作时,就很适合并发异步编程,比如爬虫的多URL的网络请求,多文件的读写操作等。

3. 如何并发异步编程?

分为三种,1. 线程 2. 进程 3.协程,这里先复习线程和进程,协程后面再复习。

多线程写法一

"""1. 创建任务2. 实例化线程对象3. 启动任务
"""
from threading import Thread# 创建任务
def func(name):for i in range(100):print(f"{i}. My name is {name}")if __name__ == "__main__":# 实例化线程对象t1 = Thread(target=func, args=("JayChou",))t2 = Thread(target=func, args=("Apphao",))# 启动任务t1.start()t2.start()

多线程写法二

"""1. 自定义类继承Thread2. 通过__init__函数传参3. 重写run方法
"""
from threading import Thread# 自定义类继承Thread
class MyThread(Thread):# 通过__init__函数传参def __init__(self, name):# 初始化时必须先调父类的初始化函数super(MyThread, self).__init__()self.name = name# 重写run方法def run(self):for i in range(100):print(f"{i}. My name is {self.name}")if __name__ == "__main__":t1 = MyThread("JayChou")t2 = MyThread("Apphao")t1.start()t2.start()

线程池

线程池的好处是保护系统,防止程序无限制的开设线程,而导致资硬件源耗尽。
线程池的基本用法
"""1. 定义任务2. 开启线程池3. 提交任务
"""
from concurrent.futures import ThreadPoolExecutor# 定义任务
def func(name):for i in range(10):print(f"{i}. My name is {name}")if __name__ == "__main__":# 开启线程池with ThreadPoolExecutor(10) as t:for i in range(100):# 提交任务t.submit(func, f"JayChou {i}")
可以获得线程执行结果的写法
"""1. 定义任务2. 开启线程池3. 提交任务并添加回调函数4. 定义回调函数
"""
from concurrent.futures import ThreadPoolExecutor
import time# 定义任务
def func(name, t):print(f"My name is {name}")time.sleep(t)return name# 定义回调函数
def fn(res):print(res.result())if __name__ == "__main__":# 开启线程池with ThreadPoolExecutor(3) as t:# 提交任务并添加回调函数t.submit(func, "JayChou", 2).add_done_callback(fn)t.submit(func, "Egon", 3).add_done_callback(fn)t.submit(func, "Apphao", 1).add_done_callback(fn)
add_done_callback()指定的回调函数在线程执行完成后会立即执行,由于每个线程的执行时间是不确定的,因此回调函数的执行顺序也是不确定的,返回值的顺序时不确定的,如果想要返回值顺序确定,则改用下面写法。
"""1. 定义任务2. 开启线程池3. 通过map提交任务,并接受map的返回值4. for循环获取map的返回值
"""
from concurrent.futures import ThreadPoolExecutor
import time# 定义任务
def func(name, t):print(f"My name is {name}")time.sleep(t)return nameif __name__ == "__main__":# 开启线程池with ThreadPoolExecutor(3) as t:result = t.map(func, ["JayChou", "Egon", "Apphao"], [2, 3, 1])# map的返回值时生成器,通过for循环取出里面的内容for r in result:print(r)

多进程写法一:和多线程几乎一摸一样

"""1. 创建任务2. 实例化进程对象3. 启动任务
"""
from multiprocessing import Process# 创建任务
def func(name):for i in range(100):print(f"{i}. My name is {name}")if __name__ == "__main__":# 实例化进程对象p1 = Process(target=func, args=("JayChou",))p2 = Process(target=func, args=("Apphao",))# 启动任务p1.start()p2.start()

多进程写法二:和多线程几乎一摸一样

"""1. 自定义类继承Process2. 通过__init__函数传参3. 重写run方法
"""
from multiprocessing import Process# 自定义类继承Process
class MyProcess(Process):# 通过__init__函数传参def __init__(self, name):# 初始化时必须先调父类的初始化函数super(MyProcess, self).__init__()self.name = name# 重写run方法def run(self):for i in range(100):print(f"{i}. My name is {self.name}")if __name__ == "__main__":p1 = MyProcess("JayChou")p2 = MyProcess("Apphao")p1.start()p2.start()

进程池

进程池的基本用法,和线程池几乎一模一样
"""1. 定义任务2. 开启进程池3. 提交任务
"""
from concurrent.futures import ProcessPoolExecutor# 定义任务
def func(name):for i in range(10):print(f"{i}. My name is {name}")if __name__ == "__main__":# 开启进程池with ProcessPoolExecutor(10) as t:for i in range(100):# 提交任务t.submit(func, f"JayChou {i}")
获取进程的返回值,和线程池几乎一模一样
"""1. 定义任务2. 开启进程池3. 提交任务并添加回调函数4. 定义回调函数
"""
from concurrent.futures import ProcessPoolExecutor
import time# 定义任务
def func(name, t):print(f"My name is {name}")time.sleep(t)return name# 定义回调函数
def fn(res):print(res.result())if __name__ == "__main__":# 开启进程池with ProcessPoolExecutor(3) as t:# 提交任务并添加回调函数t.submit(func, "JayChou", 2).add_done_callback(fn)t.submit(func, "Egon", 3).add_done_callback(fn)t.submit(func, "Apphao", 1).add_done_callback(fn)
有序地获得进程池中返回值,和线程池几乎一模一样
"""1. 定义任务2. 开启进程池3. 通过map提交任务,并接受map的返回值4. for循环获取map的返回值
"""
from concurrent.futures import ProcessPoolExecutor
import time# 定义任务
def func(name, t):print(f"My name is {name}")time.sleep(t)return nameif __name__ == "__main__":# 开启进程池with ProcessPoolExecutor(3) as t:result = t.map(func, ["JayChou", "Egon", "Apphao"], [2, 3, 1])# map的返回值时生成器,通过for循环取出里面的内容for r in result:print(r)

何时使用多线程,何时使用多进程?

1. 多线程:任务相对统一,互相特别相似
2. 多进程:多个任务相互独立,很少有交集

逆向爬虫08 并发异步编程相关推荐

  1. Windows并发异步编程(1)JAVA多线程

    本文在基于C/C++/Windows相关知识的基础上,初步封装一个像JAVA一样的多线程类–Win32Thread.使操作线程能像JAVA一样两步搞定: 继承基类Win32Thread,并覆盖其中的r ...

  2. java 如何只暴露接口_Java并发异步编程,原来十个接口的活现在只需要一个接口就搞定...

    什么?对你没有听错,也没有看错 ..多线程并发执行任务,取结果归集~~ 不再忧愁-. 引言 先来看一些APP的获取数据,诸如此类,一个页面获取N多个,多达10个左右的一个用户行为数据,比如:点赞数,发 ...

  3. Java并发异步编程,原来十个接口的活现在只需要一个接口就搞定!

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:锦成同学 juejin.im/post/5d3c46d2f265da1b9163db ...

  4. Java 并发异步编程,原来十个接口的活现在只需要一个接口就搞定!

    引言 多线程并发执行任务,取结果归集 状态 队列 CAS操作 实战演练 总结 小甜点 什么?对你没有听错,也没有看错 ..多线程并发执行任务,取结果归集~~ 不再忧愁....感谢大家的双击+点赞和关注 ...

  5. 实例解析C++多线程并发---异步编程

    线程同步主要是为了解决对共享数据的竞争访问问题,所以线程同步主要是对共享数据的访问同步化(按照既定的先后次序,一个访问需要阻塞等待前一个访问完成后才能开始).这篇文章谈到的异步编程主要是针对任务或线程 ...

  6. python3异步task_并发,异步编程_Python中的asyncio模块中的Future和Task的区别?,并发,异步编程,python,asyncio - phpStudy...

    Python中的asyncio模块中的Future和Task的区别? 问题一 按照官方文档的描述,Task是Futrue的一个subclass,标准库中也分别提供了create_task和create ...

  7. Generator(生成器),入门初基,Coroutine(原生协程),登峰造极,Python3.10并发异步编程async底层实现

    普遍意义上讲,生成器是一种特殊的迭代器,它可以在执行过程中暂停并在恢复执行时保留它的状态.而协程,则可以让一个函数在执行过程中暂停并在恢复执行时保留它的状态,在Python3.10中,原生协程的实现手 ...

  8. springboot异步和切面_Spring异步编程 你的@Async就真的异步吗?异步历险奇遇记

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们biaji一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: 注 ...

  9. springboot异步和切面_Spring异步编程 | 你的@Async就真的异步吗 ☞ 异步历险奇遇记...

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们bia~ji~一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: ...

最新文章

  1. 设计模式之创建型汇总
  2. SQL获取所有数据库名、表名、储存过程以及参数列表
  3. Android中removeCallbacks失效原因
  4. select weui 动态加载数据_weui中的picker使用js进行动态绑定数据问题
  5. mysql差几年,MySQL两时间计算、年份差、月份差、天数差
  6. mac设置首页访问php,mac系统下php项目除了首页全访问不了
  7. Nacos 集群整合 Nginx 实现反向代理、负载均衡_03
  8. 计划任务如何使用 java_java – 如何计划任务以定期间隔运行?
  9. oracle ora 08103,ORA-08103: 对象不再存在
  10. LCA RMQ+ST表学习笔记
  11. 虚拟主机金华php空间,金华虚拟主机_金华云虚机_金华主机申请_金华网站空间_爱名网(www.22.cn)...
  12. unity3d 取锚点位置_点的投影
  13. m3u8 文件代码片段.
  14. 墨菲定律吉德林法则吉尔伯特定律沃尔森法则福克兰定律
  15. mybatis看这一篇就够了,简单全面一发入魂
  16. GB28181系列笔记-语音对讲功能
  17. django中cookie模板引用
  18. tomcat 中部署的应用响应json数据乱码解决办法
  19. excel一列数据中每个数重复固定次数
  20. part01.03 委托与 Lambda 表达式(三):Lambda 表达式

热门文章

  1. 二极管在LDO电路中的几种常见用法
  2. 谷歌浏览器chrome官方下载网址
  3. 入驻 【集简云开发者平台】,SDK嵌入接口文档介绍
  4. 计算机系统记忆部件是,什么是计算机系统的记忆部件
  5. Mouse Controler(手机操控鼠标)
  6. 最小公倍数的多种求法(C++代码实现)
  7. 利用连按 5 下 Shift 漏洞破解 win7 开机密码(原理以及实现)
  8. mybatis定义别名
  9. 24C02 EEPROM多个字节连续写入乱码问题解决
  10. php考勤系统微信小程序