目录

阻塞

常见的阻塞形式

非阻塞

同步

异步

多进程

协程

asyncio 库基础与演示

基础

event_loop

coroutine

task

future

演示

简单的协程函数执行

Task(协程对象转换成任务对象执行)

Task对象的绑定回调操作


本文为学习笔记,部分内容为老师所写,非纯原创

        爬虫是 IO 密集型任务(输入input/输出output),比如我们使用 requests 库来爬取某个站点的话,发出一个请求之后,程序必须要等待网站返回响应之后才能接着运行(请求出去了得等它回来),而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何的事情。因此,有必要提高程序的运行效率,异步就是其中有效的一种方法。

阻塞

        程序未得到所需计算资源时被挂起的状态,即程序在等待某个操作完成期间,自身无法继续处理其他的事情,程序在该操作上是阻塞的。

常见的阻塞形式

  • 网络 I/O 阻塞:数据的传输速度较慢或无法立即获得所需的数据,可能是由于网络延迟、带宽限制、远程服务器负载过重引起。
  • 磁盘 I/O 阻塞:当负载增大时,系统吞吐量不能有效增大,CPU不能线性增长,则可能是磁盘I/O出现阻塞
  • 用户输入阻塞:在程序中等待用户输入时,程序暂停执行,直到用户输入完成或者超时。在阻塞状态下,程序无法进行其他操作,只能等待用户的响应。

非阻塞

        程序在等待某操作过程中,自身不被阻塞,可以继续处理其他的事情,则称该程序在该操作上是非阻塞的。非阻塞并不是在任何程序级别、任何情况下都可以存在的。仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。

同步

        不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,我们称这些程序单元是同步执行的。例如购物系统中更新商品库存,需要用“行锁”作为通信信号,让不同的更新请求强制排队顺序执行,那更新库存的操作是同步的。简言之,同步意味着有序(程序需要按照一定的顺序去执行,顺序不能变

异步

        为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。简言之,异步意味着无序。(效率提高了,没有先后顺序,互不交互,互不干扰

多进程

        多进程就是利用 CPU 的多核优势,在同一时间并行地执行多个任务,可以大大提高执行效率。(一般进程数不超过电脑的核数,过多效率会变低。

协程

        协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。协程本质上是个单进程,协程相对于多进程来说,无需线程上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。我们可以使用协程来实现异步操作,比如在网络爬虫场景下,我们发出一个请求之后,需要等待一定的时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他的事情,等到响应得到之后才切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是协程的优势。(协程是异步操作,不能保证请求顺序,可以保留上一次调用时的状态,爬虫时可以排序进行数据处理或以分类进行抓取数据

asyncio 库基础与演示

基础

event_loop

        事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。(执行协程函数,协程函数想要执行就必须把函数放到事件池里,用事件池的方法去执行

coroutine

        中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。(定义协程函数需要在函数前加async 关键字,调用协程函数时不会直接返回返回值,需要把协程函数放到事件池里去执行

task

        任务,它是对协程对象的进一步封装,包含了任务的各个状态。(可以把协程对象转成任务对象,比协程对象多了当前对象执行进度,running、finished......)

future

        代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。async/await 关键字,是从 Python 3.5 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。(await后面加的都是需要等待的东西,比如请求一个东西,需要等这个东西返回才能拿这个东西去做别的,这时候就需要加await

演示

简单的协程函数执行

        async 定义的方法就会变成一个无法直接执行的 coroutine 对象,必须将其注册到事件循环中才可以执行

import asyncio
#引一个协程的包
async def execute(x):
#定义协程函数execute
    print('Number:', x)
coroutine = execute(666)
#直接执行函数,由下图结果可看出直接返回了一个coroutine协程对象,并没有执行方法
print('Coroutine:', coroutine)
print('After calling execute')
loop = asyncio.get_event_loop()
#建一个事件池
loop.run_until_complete(coroutine)
#执行协程函数
print('After calling loop')

 

Task(协程对象转换成任务对象执行)

        task比 coroutine 对象多了运行状态,running、 finished 等,可以用这些状态来获取协程对象的执行情况。在上面的例子中,将 coroutine 对象传递给 run_until_complete 方法的时候,实际上它进行了一个操作就是将 coroutine封装成了 task 对象。 task 也可以显式地进行声明(其实没必要多此一举):
import asyncio
async def execute(x):
    print('Number:', x)
    return x
coroutine = execute(666)
print('Coroutine:', coroutine)
print('After calling execute')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
#调用了loop的create_task方法将coroutine对象转化为了task对象
print('Task:', task)
loop.run_until_complete(task)
print('Task:', task)
print('After calling loop')

        执行第一个print('Task:', task)时,还没有run这个任务,所以返回状态是pending(等待执行),执行第二个print('Task:', task)时,任务已经结束,所以返回状态是finished(已完成),并且result 变成了coroutine返回结果666。

  

        定义 task 对象还可以直接通过 asyncio ensure_future 方法,返回结果也是 task对象,这样 即使还没有声明 loop 也可以提前定义好 task 对象
import asyncio
async def execute(x):
    print('Number:', x)
    return x
coroutine = execute(666)
print('Coroutine:', coroutine)
print('After calling execute')
task = asyncio.ensure_future(coroutine)
# asyncio.ensure_future直接定义task对象
print('Task:', task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task:', task)
print('After calling loop')

 

        可以发现运行结果和第一种方法一样

Task对象的绑定回调操作

        call_on 方法里面没有任何 print 语句。 call_back 方法接收一个参数,是 task 对象,然后调用 print 打印  task 对象的结果。 当 coroutine 对象执行完毕之后,执行声明的 call_back 方法。实现这样的效果只需要调用 add_done_callback 方法即可,我们将 call_back 方法传递给了封装好的 task 对象,这样当 task 执行完毕之后就可以调用 call_back 方法了,同时 task 对象还会作为参数传递给 call_back 方法,调用 task 对象的 result 方法就可以获取返回结果了。
import asyncio
import requests
async def call_on():
    status = requests.get('https://www.baidu.com')
    return status
# 返回状态码
def call_back(task):
    print('Status:', task.result())
corountine = call_on()
task = asyncio.ensure_future(corountine)
# 将协程对象转成任务task
task.add_done_callback(call_back)
# 给任务绑定回调函数
print('Task:', task)
loop = asyncio.get_event_loop()
# 注册事件池用来执行任务
loop.run_until_complete(task)
print('Task:', task)

        个人理解就是先把coroutine转成任务对象,然后给任务对象绑一个回调函数,这样执行完任务后就会执行回调函数,即执行完task(就是call_on),把返回值传给call_back,执行call_back。

 

        不用回调函数,直接在 task 运行完毕之后也能直接调用 result 方法获取结果
import asyncio
import requests
async def call_on():
    status = requests.get('https://www.baidu.com')
    return status
def call_back(task):
    print('Status:', task.result())
corountine = call_on()
task = asyncio.ensure_future(corountine)
print('Task:', task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task:', task)
print('Task:', task.result())
#任务执行后,直接调用result
Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐