目录
前言
协程的主要概念
一个简单的协程
注册协程的三种方式
使用协程的一般步骤
并发运行协程
gather与wait(此示例来自ChatAl)
aiohttp(此示例来自ChatAl)
异步上下文管理器
通过生成器理解协程
爬虫是IO密集型任务,在爬取过程中需要等待网站的响应,在这个等待过程中我们可以使用并发来加快爬取的效率,实现并发的可以是多线程多进程,那为什么还要使用协程呢?
并发主要是利用程序IO操作时CPU空闲时间调用其他程序运行的方式来提高资源的利用率,但是不管是进程还是线程,切换时都会带来巨大开销,CPU内核的切换,上下文的保存和恢复等。而协程,相较线程来说更加轻量级的;它不被操作系统的内核所管理,完全由程序所控制,不需要CPU内核的切换,也能实现并发。
evenloop:事件循环中心,提供了注册,取消,回调任务和回调方法,是一个永久循环,注册函数到这个事件循环中心中,当满足某些条件时再调用此函数。
coroutine:协程,表示协程的对象类型,用async关键字声明的方法为协程方法
await:在协程方法中,await关键字会将耗时等待的操作挂起,让出控制权。如果协程在执行的时候遇到await关键字,事件循环就会将此协程挂起,转而执行别的协程,直到其他协程挂起或者执行完毕。
await后面可等待对象的类型:coroutine,task,future
可以将协程注册到事件循环中心,等待被调用。
import asyncio
import time
async def a():
print('suspending a')
await asyncio.sleep(3)
print('dead a')
async def b():
print('suspending b')
await asyncio.sleep(2)
print('dead b')
async def main():
print(f"started at {time.strftime('%X')}")
await a()#挂起协程a
await b()#挂起协程b
print(f"finished at {time.strftime('%X')}")
if __name__ == '__main__':
asyncio.run(main())
started at 10:59:25
suspending a
dead a
suspending b
dead b
finished at 10:59:30
这个程序声明了三个协程,分别是a,b,main
可以发现都是通过async声明一个协程,并在协程方法中通过await关键字挂起耗时操作
观察main协程方法,可以发现在方法中通过await挂起a,b协程(它们是可等待对象),main方法中传递协程a和协程b,这样我们只需将main注册到事件循环中,那么协程都可以执行了。
注意:协程只用注册到事件循环中才能正常执行。
1、由上面的协程程序,可以发现通过asncio.run()方法可以将协程注册到事件循环中,这个方法是python3.7出现的高层API。用于运行一个协程方法并管理底层的事件循环。当然还有下面的两种方法
2、
loop = asyncio.get_event_loop()#获取当前的事件循环
loop.run_until_complete(main())#运行协程
3、
loop=asyncio.new_event_loop()#创建一个新的事件循环
loop.run_until_complete(main())#运行协程
定义异步函数:使用async
关键字定义一个异步函数,可以在函数名前面加上async
修饰符。异步函数可以包含await
关键字来等待其他的异步操作完成。
创建事件循环:在Python中,异步操作通常在事件循环(Event Loop)中执行。通过调用asyncio.get_event_loop()
来获取默认的事件循环对象,或使用asyncio.new_event_loop()或者
asyncio.run()来创建新的事件循环对象。
执行协程:通过await
关键字等待协程函数的执行结果。将协程函数包装在asyncio.run()
函数中,或使用事件循环的run_until_complete()
方法来启动协程的执行。
根据上述的协程程序可以发现,程序运行了5秒,并没有实现并发,程序中其实是顺序运行的,那么该如何让它实现并发呢?
先了解下task对象
Task:Task对象用于封装一个协程函数,包括协程的各个状态,以便在事件循环中进行调度和执行。Task对象提供了一种方便、灵活和高效的方式来管理异步任务,实现并发执行,并且提供了错误处理、回调函数、取消等额外的功能。
封装的函数会被自动调度执行, 函数以异步方式并发运行协程
1、asyncio.create_task() 封装协程
import asyncio
import time
async def a():
print('suspending a')
await asyncio.sleep(3)
print('dead a')
async def b():
print('suspending b')
await asyncio.sleep(2)
print('dead b')
async def main():
print(f"started at {time.strftime('%X')}")
taska=asyncio.create_task(a())
taskb=asyncio.create_task(b())
await taska#挂起协程a
await taskb#挂起协程b
print(f"finished at {time.strftime('%X')}")
if __name__ == '__main__':
asyncio.run(main())
started at 11:21:34
suspending a
suspending b
dead b
dead a
finished at 11:21:37
运行时间3秒,由此可以协程实现并发运行了,当然将协程封装成task对象还有下面两种方式
2、asncio.ensure_future :接收的参数不仅包括协程还包括Future对象或者是awaitable对象
import asyncio
import time
async def a():
print('suspending a')
await asyncio.sleep(3)
print('dead a')
async def b():
print('suspending b')
await asyncio.sleep(2)
print('dead b')
async def main():
print(f"started at {time.strftime('%X')}")
taska=asyncio.ensure_future(a())
taskb=asyncio.ensure_future(b())
await taska#挂起协程a
await taskb#挂起协程b
print(f"finished at {time.strftime('%X')}")
if __name__ == '__main__':
asyncio.run(main())
3、loop.create_task:接收的参数为一个协程
import asyncio
import time
async def a():
print('suspending a')
await asyncio.sleep(3)
print('dead a')
async def b():
print('suspending b')
await asyncio.sleep(2)
print('dead b')
async def main():
print(f"started at {time.strftime('%X')}")
# taska=asyncio.create_task(a())
# taskb=asyncio.create_task(b())
# taska=asyncio.ensure_future(a())
# taskb=asyncio.ensure_future(b())
loop=asyncio.get_event_loop()
taska=loop.create_task(a())
taskb=loop.create_task(b())
await taska#挂起协程a
await taskb#挂起协程b
print(f"finished at {time.strftime('%X')}")
if __name__ == '__main__':
asyncio.run(main())
运行结果同样为3秒 如下:
started at 11:23:44
suspending a
suspending b
dead b
dead a
finished at 11:23:47
如果我们要并发运行很多协程方法并获取到结果,仅通过封装成task已经力不从心了,ok,gather和wait可以派上用场了。
gather:让多个协程实现并发执行,返回值为协程的运行结果,而且它会按照输入协程的顺序返回
wait:让多个协程实现并发执行,返回值有两项,第一项表示为完成任务的列表,第二项表示等待future任务完成的结果,每个任务都是task实例,可以使用task.result()获取协程返回值,此外wait还支持返回的时机。
1、当使用asyncio.gather()
函数时,可以简单地传递多个协程函数(或者其他可等待对象),然后它会同时执行这些协程,并收集它们的结果。
import asyncio
async def coroutine1():
print("Coroutine 1 started")
await asyncio.sleep(2) # 模拟耗时操作
print("Coroutine 1 completed")
return "Result from Coroutine 1"
async def coroutine2():
print("Coroutine 2 started")
await asyncio.sleep(1) # 模拟耗时操作
print("Coroutine 2 completed")
return "Result from Coroutine 2"
async def main():
tasks = [coroutine1(), coroutine2()]
results = await asyncio.gather(*tasks) # 并发执行协程,并收集结果
print("Results:", results)
asyncio.run(main())
在这个例子中,我们定义了两个协程函数coroutine1()
和coroutine2()
。每个协程函数打印开始和完成的消息,然后使用asyncio.sleep()
来模拟耗时操作。
在main()
函数中,我们创建一个任务列表tasks
,包含了这两个协程函数。然后,我们使用await asyncio.gather(*tasks)
将这些任务传递给asyncio.gather()
函数。通过使用*tasks
语法,我们将任务列表中的每个任务作为单独的参数传递给asyncio.gather()
。
asyncio.gather()
函数会同时运行这些协程函数,并在所有协程都完成后返回结果列表。在这个示例中,我们将结果赋值给results
变量,并打印出来。
最后,我们使用asyncio.run(main())
来运行主协程。
2、wait
asyncio.wait()
函数用于并发执行多个协程任务,并等待它们的完成。它接受一个可迭代的协程任务集合作为参数,并返回一个包含已完成任务集合和未完成任务集合的元组。
import asyncio
async def coroutine1():
print("Coroutine 1 started")
await asyncio.sleep(2) # 模拟耗时操作
print("Coroutine 1 completed")
return "Result from Coroutine 1"
async def coroutine2():
print("Coroutine 2 started")
await asyncio.sleep(1) # 模拟耗时操作
print("Coroutine 2 completed")
return "Result from Coroutine 2"
async def main():
tasks = [coroutine1(), coroutine2()]
done, pending = await asyncio.wait(tasks) # 并发执行协程,并等待完成
# 处理已完成的任务
for task in done:
result = await task # 获取任务的结果
print("Task result:", result)
asyncio.run(main())
在这个例子中,我们定义了两个协程函数coroutine1()
和coroutine2()
,它们模拟了耗时的操作。
在main()
函数中,我们创建了一个任务列表tasks
,包含了这两个协程函数。
使用await asyncio.wait(tasks)
来等待并发执行这些协程任务。asyncio.wait()
函数返回一个元组(done, pending)
,其中done
是已完成的任务集合,pending
是未完成的任务集合。
我们通过遍历done
集合,并使用await task
获取各个任务的结果。这里的task
是asyncio.Task
对象,我们通过await
关键字等待并获取任务的结果。
最后,我们使用asyncio.run(main())
来运行主协程。
aiohttp是一个基于asyncio的异步HTTP网络模块,他提供了服务端和客户端。在客户端中我们可以发起请求,类似于使用requests发送的一个HTTP请求。注意:requests模块不能在协程中使用,因为requests模块是同步的。
import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
url = "https://api.example.com/data" # 替换为你要请求的URL
response = await fetch(url)
print("Response:", response)
asyncio.run(main())
在这个示例中,我们首先定义了一个fetch()函数,它使用aiohttp发送异步的HTTP GET请求。
在fetch()函数中,我们使用aiohttp.ClientSession()创建一个客户端会话对象。然后,我们使用session.get(url)发送GET请求,并使用response.text()方法获取响应的文本内容。
在main()函数中,我们定义了要请求的URL,然后调用fetch()函数并等待其返回响应结果。最后,我们打印出响应的内容。
需要注意的是,aiohttp.ClientSession()是一个上下文管理器,使用async with语法来确保会话在请求完成后正确关闭和释放资源。
最后,我们使用asyncio.run(main())来运行主协程。
它相当于一个协程,在异步编程中,我们可以使用async with
语法来使用上下文管理器。上下文管理器提供了进一步管理异步任务执行的能力,并确保在进入和退出上下文时正确地处理资源分配和清理。异步上下文管理器是Python 3.7中引入的一个特性,用于在异步环境中更好地管理资源。
协程的实现原理是利用了生成器的特性,下面通过生成器来简单了解协程的原理
这个任务实现需要三个步骤,每个步骤在处理前都通过yield交出控制权,
def test(data):
print('task with data {} starter'.format(data))
yield
print('step one for data {} finished'.format(data))
yield
print('step two for data {} finished'.format(data))
yield
print('step over for data {} finished'.format(data))
testa=test('a')
if __name__ == '__main__':
print(type(testa))
next(testa)
next(testa)
next(testa)
task with data a starter
step one for data a finished
step two for data a finished
step over for data a finished
Traceback (most recent call last):
File "d:/python/repitile/request/asnciolearning.py", line 43, in
next(testa)
StopIteration
协程调度
def test(data):
print('task with data {} starter'.format(data))
yield
print('step one for data {} finished'.format(data))
yield
print('step two for data {} finished'.format(data))
yield
print('step over for data {} finished'.format(data))
if __name__ == '__main__':
test_a=test('a')
test_b=test('b')
test_c=test('c')
#调度协程
next(test_a)
next(test_a)
next(test_a)
next(test_b)
next(test_c)
next(test_b)
next(test_c)
next(test_b)
next(test_c)
task with data a starter
step one for data a finished
step two for data a finished
task with data b starter
task with data c starter
step one for data b finished
step one for data c finished
step two for data b finished
step two for data c finished
好的,就到这里了,屏幕前的你真好看