原文作者:我辈李想
版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。
在 Python 中,同步和异步通常是指代码执行的模式。
同步:当程序执行同步操作时,程序将等待该操作完成,然后才执行下一段代码。这种模式被称为同步模式。在同步模式下,程序必须等待操作完成之后才能继续执行下一步操作。
异步:在异步模式下,程序执行操作时,不需要等待该操作完成,而是可以继续执行其他操作。当该操作完成后,程序将通知其结果。这使得程序可以执行多个操作而不必等待每个操作完成。异步操作通常使用回调函数来处理操作结果。
Python中的协程(coroutine)是一种异步编程的方式,在 Python3.5版本后,可以使用 async 和 await 关键字来实现异步操作。async 关键字用于定义异步函数,而 await 关键字用于等待异步函数的结果。异步函数通常返回一个协程对象,该对象可以在 await 关键字后使用。
另外,Python 还提供了 asyncio 模块来实现异步操作,该模块提供了事件循环和协程的支持。使用事件循环可以管理多个协程,而协程可以在事件循环中运行,以实现非阻塞的 I/O 操作。
异步数据库查询:可以使用异步的数据库驱动程序(如aiomysql
、aiopg
等)进行数据库查询操作,使用await
关键字来等待查询结果的返回,从而避免了阻塞整个进程。
异步HTTP请求:使用异步的HTTP客户端库(如httpx
、aiohttp
等)发送异步的HTTP请求,使用await
关键字等待响应结果的返回,从而避免了阻塞整个进程。
异步文件读写操作:在处理文件读写、网络通信等IO操作时,可以使用异步的IO库(如aiofiles
、asyncio
等)进行异步操作,通过await
关键字等待IO操作的完成,从而提高程序的性能。
异步任务调度:使用FastAPI内置的BackgroundTasks
类可以异步执行后台任务,例如发送邮件、推送通知等,通过await
关键字等待任务的完成,从而不会阻塞主请求的处理。
想要在我们的程序中使用协程,需要明确三个步骤,异步操作、任务队列、事件循环。
这里的异步操作就是上边提到的非阻塞的 I/O 操作,我们需要使用 async 和 await 关键字完善函数。await 只能在async 函数中使用,同步函数无法调用。
import asyncio
# io等待
async def download(url):
await asyncio.sleep(1)
# 网络请求
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async函数返回的是协程对象,无法直接调用。
基于python同步和异步的差异,我们无法直接运行fetch,asyncio提供了事件循环执行异步函数。
# python 3.5/3.6
import asyncio
url = 'https://www.example.com/page1'
loop = asyncio.get_event_loop()
results = loop.run_until_complete(fetch(url))
# python 3.7
import asyncio
url = 'https://www.example.com/page1'
asyncio.run(fetch(url))
# 网络请求
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
url = 'https://www.example.com/page1'
asyncio.run(fetch(url))
我们需要批量执行fetch函数,fetch返回的是协程对象,我们需要把任务放在队列中,可以使用asyncio.gather或asyncio.wait方法。
async def crawl(urls):
tasks = []
for url in urls:
tasks.append(fetch(url, session))
return await asyncio.gather(*tasks)
批量任务示例代码
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def crawl(urls):
tasks = []
for url in urls:
tasks.append(fetch(url))
return await asyncio.gather(*tasks)
urls = ["https://www.example.com/page1",
"https://www.example.com/page2",
"https://www.example.com/page3"]
asyncio.run(crawl(urls))
其他示例
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
urls = ["https://www.example.com/page1",
"https://www.example.com/page2",
"https://www.example.com/page3"]
tasks = [fetch(url) for url in urls]
asyncio.run(asyncio.wait(tasks))
同步函数调用asyncio.run的返回值是一个列表,与任务队列的顺序一致。
def main():
urls = ["https://www.example.com/page1",
"https://www.example.com/page2",
"https://www.example.com/page3"]
results = asyncio.run(crawl(urls))
for result in results:
print('result')
return results
main()
源码如下:
import asyncio
import datetime
import aiohttp
import requests
def old(url):
responseStr = requests.get(url=url).text
async def fetch(url,):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def crawl(urls):
tasks = []
for url in urls:
tasks.append(fetch(url))
return await asyncio.gather(*tasks)
def main():
urls = ["https://www.example.com/page1", "https://www.example.com/page2", "https://www.example.com/page2",
"https://www.example.com/page2", "https://www.example.com/page2", "https://www.example.com/page3"]
loop = asyncio.get_event_loop()
results = loop.run_until_complete(crawl(urls))
for result in results:
print(result)
def old_main():
urls = ["https://www.example.com/page1", "https://www.example.com/page2", "https://www.example.com/page2",
"https://www.example.com/page2", "https://www.example.com/page2", "https://www.example.com/page3"]
for url in urls:
a = old(url)
if __name__ == "__main__":
# 运行异步主函数
a_t = datetime.datetime.now()
# old_main()
main()
b_t = datetime.datetime.now() - a_t
print("time", b_t)
Python中协程的实现主要有两种方式:使用生成器(yield关键字)和使用async/await关键字。
def coroutine():
print("Coroutine started")
value = yield
print("Coroutine received:", value)
c = coroutine()
next(c) # 启动协程
c.send("Hello") # 发送数据给协程
在上面的示例中,coroutine函数是一个生成器函数,通过yield关键字来实现协程的暂停和恢复。首先需要调用next©来启动协程,然后通过c.send(value)来发送数据给协程,协程会在yield处暂停,并返回接收到的数据。
import asyncio
async def coroutine():
print("Coroutine started")
value = await asyncio.sleep(1) # 模拟异步操作
print("Coroutine received:", value)
asyncio.run(coroutine())
在上面的示例中,coroutine函数是一个协程函数,通过async关键字来定义。使用await关键字来等待异步操作的完成,并返回结果。在这个例子中,使用asyncio.sleep(1)来模拟一个异步操作,等待1秒钟后返回结果。
需要注意的是,在使用async/await关键字实现协程时,需要借助于事件循环(event loop)来驱动协程的执行。使用asyncio.run()来创建一个事件循环,并执行协程函数。
无论是使用生成器还是async/await关键字,协程都可以在遇到IO等待时暂停执行,并将控制权交给事件循环,从而实现了非阻塞的异步操作。这使得协程成为处理IO密集型任务的理想选择。
要编写一个协程的第三方库,需要深入了解Python的协程机制以及协程库的设计原理。以下是一个简单示例,展示了一个使用生成器实现的简易协程库:
import queue
class Coroutine:
def __init__(self, func):
self.func = func
self.queue = queue.Queue()
def start(self):
self._step()
def _step(self, value=None, exc=None):
try:
if exc:
result = self.func.throw(exc)
else:
result = self.func.send(value)
if isinstance(result, Coroutine):
result.queue = self.queue
result._step()
else:
self.queue.put(result)
except StopIteration as e:
self.queue.put(e.value)
except Exception as e:
self.queue.put(e)
finally:
if not self.queue.empty():
self.queue.get_nowait()._step()
def send(self, value=None):
self._step(value)
def throw(self, exc):
self._step(exc=exc)
def join(self):
return self.queue.get()
使用这个简易协程库可以实现协程的调度和运行。以下是一个示例,展示了如何使用这个库来调度协程的执行:
def coroutine1():
while True:
value = yield "coroutine1"
print("coroutine1 received:", value)
def coroutine2():
while True:
value = yield "coroutine2"
print("coroutine2 received:", value)
def main():
coro1 = Coroutine(coroutine1())
coro2 = Coroutine(coroutine2())
coro1.start()
coro2.start()
for i in range(5):
print("Sending value:", i)
result1 = coro1.join()
print("Result from coroutine1:", result1)
result2 = coro2.join()
print("Result from coroutine2:", result2)
if __name__ == "__main__":
main()
这个示例中,我们定义了两个简单的协程函数coroutine1
和coroutine2
,它们会不断地接收值并打印出来。在main
函数中,我们创建了两个Coroutine
对象分别对应这两个协程函数,并调用start
方法来启动它们的执行。然后我们通过调用join
方法来等待协程执行完成,并获得协程的返回值。
这只是一个简单的示例,实际编写一个全功能的协程库需要考虑更多的细节和实现方式。但这个示例可以帮助你理解协程库的基本工作原理和实现方式。
当执行大量cpu计算和io操作时,可以通过多进程+协程的方式提高效率,多进程可以充分利用多核提高cpu计算能力,协程可以异步减少io等待。有一个第三方库aiomultiprocess,让你能用几行代码就实现多进程与协程的组合。
pip install aiomultiprocess
import asyncio
import httpx
from aiomultiprocess import Pool
async def get(url):
async with httpx.AsyncClient() as client:
resp = await client.get(url)
return resp.text
async def main():
urls = [url1, url2, url3]
async with Pool() as pool:
async for result in pool.map(get, urls):
print(result) # 每一个URL返回的内容
if __name__ == '__main__':
asyncio.run(main())