Tommonkey

All greatness comes from a brave beginning

0%

爬虫之异步协程学习总结

协程

协程:英文名(Coroutine),又称为微线程,线程是系统级别的,它们由操作系统调度。而协程则是程序级别的由程序根据需要自己调度。在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。
通俗易懂的说协程就是通过一个线程来实现代码块(函数)之间的切换执行。
协程函数:函数前面加上async即为协程函数,比如:async def function()。
协程对象:执行协程函数得到的协程对象。执行协程函数创建协程对象,函数内部代码不会执行。

协程的意义

为什么要用协程?回答这个问题之前回想一下小学我们做过的数学题:水壶烧开水需要20分钟,可我们不能傻傻得等着啊,这段空闲的时间我们还可以扫地,吃饭呢,这样就节约的时间,上学就不会迟到。协程也是这么个道理,当计算机进行IO输出时,CPU是处于空闲的状态,这显然是浪费时间也浪费性能,所以通过协程,让CPU休息的时候取执行娶她的事情。

实现协程的几种方法:

  • greenlet:早期模块
  • yield关键字
  • asyncio装饰器(python3.4支持)
  • async,await关键字(主流),需python3.5以上支持,本文介绍的为此种方法。

异步编程

同步与异步

同步:synchronous,一收一发,发送方得等到接收方回应才可以发送下一个信息。异步:asynchronous,发送方发送消息后不必等待接收方返回消息,就就可以进行下一步得操作。同步异步强调得是消息通信得机制

事件循环

事件循环:理解为一个死循环,不断得重复检测task中有没有任务需要执行,同时将已经执行得任务从task中移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
 # 伪代码描述
任务列表 = [task1,task2,task3,task4,.....]

while True:
可执行得任务列表,已完成得任务列表 = 去任务列表中检查所有得任务,将’可执行‘和’已完成‘得任务返回

for '就绪任务' in 可执行任务列表:
执行已就绪任务

for 已完成得任务 in 已完成得任务列表:
在任务列表中移除 已完成得任务

不断检测后发现,任务列表为空,则终止循环

await关键字

在协程函数中,执行引擎遇到await命令,就会在异步任务开始执行之后,暂停当前 async 函数的执行,把执行权交给其他任务。等到异步任务结束,再把执行权交回 async 函数,继续往下执行,相当于挂起操作。

async.run()方法

此方法加载 async 函数,启动事件循环,但此方法旨在python 3.7+以上可使用。比run_until_complete()使用更加简介和方便。

async.run(function()) # 启动协程函数

run_until_complete()方法

此方法与async.run()功能一样,它可以在python 3.5+以上可使用。

async.wait()与async.gather()

asyncio.wait 和 asyncio.gather 实现的效果是相同的,都是把所有 Task 任务结果收集起来.他们之间的更多区别请参考:这篇博文

简单实列一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import asyncio

# 协程函数
async def func1():
print("1")
await asyncio.sleep(2) # 休眠2s,交出cpu的执行权给其他任务,执行挂起操作
print("2")

async def func2():
print("a")
await asyncio.sleep(2)
print("b")

async def main():
await asyncio.gather(func1(),func2()) # asyncio.gather()方法将多个异步任务包装成一个新的异步任务,必须等到内部的多个异步任务都执行结束,这个新的异步任务才会结束

# asyncio.run() 在事件循环上监听 async 函数main的执行。等到 main 执行完了,事件循环才会终止
asyncio.run(main()) # 启动事件循环,加载协程函数

简单实列二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import time
import asyncio
import aiohttp # 异步请求模块

# 协程函数
async def func(url):
print("正在下载" + url)
# 异步协程中不能出现同步相关的代码模块,不然无法实现异步。这里的请求没有采用request,因为它属于同步请求模块,这里使用的aiohttp模块来请求实现异步
async with aiohttp.ClientSession() as session: # 创建会话对象
# 获取响应数据前要手动挂起,这里的post,get使用方法与requests模块相同
async with await session.get(url) as response: # 在asyncio中遇到io阻塞操作时,必须要手动挂起,使用await挂起操作
page_content = await response.text() # 这里可以选text(),json(),read()等响应数据格式
print(page_content)

start = time.time()

urls = [
'https://www.tommonkey.cn/2021/12/02/firewall-cmd%E5%91%BD%E4%BB%A4/',
'https://www.tommonkey.cn/2021/12/04/CentOS-python3-Java-%E5%AE%89%E8%A3%85/',
'https://www.tommonkey.cn/2021/12/07/Git-%E6%BA%90%E7%A0%81%E6%B3%84%E9%9C%B2-GitHack%E4%BD%BF%E7%94%A8/'
]

# 创建任务列表,用来存放多个任务对象
task_list = []

for url in urls:
obj_func = func(url) # 返回协程对象
task = asyncio.ensure_future(obj_func) # 创建任务对象
task_list.append(task) # 将创建好的任务对象添加到任务列表中

# 创建事件循环对象
loop = asyncio.get_event_loop()

# 将任务列表添加到事件循环中,任务列表使用asyncio.wait封装
loop.run_until_complete(asyncio.wait(task_list))

print(time.time()-start)
奖励作者买杯可乐?