python异步编程主要用于IO密集型任务,如网络请求、文件读写等,可以有效提高程序的执行效率。
1.同步编程
import requests
def fetch_url(url):
response = requests.get(url)
# 模拟网络io响应时间,程序就会在这里阻塞住。
# 这段时间也不能去处理其他事情。
# time.sleep(3)
return response.text
# 使用示例
url = "http://example.com"
result = fetch_url(url)
print(result)
2.异步编程
为了将上述同步请求转换为异步请求,我们可以使用aiohttp
库,这是一个基于asyncio
的HTTP客户端/服务器框架。
首先,需要安装aiohttp
库(如果尚未安装):
pip install aiohttp
然后,编写异步HTTP请求的代码:
import aiohttp
import asyncio
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
# 使用asyncio运行异步函数
async def main():
url = "http://example.com"
result = await fetch_url(url)
print(result)
# 不是并发的异步编程,看不出什么效果
# Python 3.7+
asyncio.run(main())
在这个异步示例中,fetch_url
函数被声明为async
,这意呀着它内部可以包含await
表达式。我们使用aiohttp.ClientSession()
来创建一个会话,然后通过会话发起异步GET请求。请求的结果是一个aiohttp.ClientResponse
对象,我们通过调用其text()
方法并等待(await
)其结果来获取响应文本。
最后,我们使用asyncio.run(main())
来运行我们的异步主程序。
通过这种方式,我们可以将同步的HTTP请求转换为异步的,从而利用asyncio
的优势来编写更高效、更易于维护的并发代码。
3.同步
HTTP请求在for循环中的性能问题:等待时间成倍增加
import time
import requests
urls = ["http://example.com", "http://example.org", "http://example.net"]
for url in urls:
response = requests.get(url)
time.sleep(3)
# 同步:3个请求耗时 3*3 秒
print("时间在白白流逝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print(response.text) # 这里会等待每个请求完成后再继续
问题表现
- 等待时间累积:随着循环的进行,整个脚本的执行时间显著增加。这是因为每个HTTP请求都需要一定的时间来完成(包括网络延迟、服务器处理时间等),而这些时间是累加的。
- 并发限制:如果所有的请求都是顺序发送的(即上一个请求完成后再发送下一个),那么总等待时间将是所有请求时间的总和。
- 资源瓶颈:如果目标服务器或网络带宽有限,连续的高频请求可能会进一步加剧等待时间,因为服务器可能无法及时处理所有请求。
原因分析
- 串行执行:
for
循环中的HTTP请求通常是串行执行的,即一个接一个地发送,没有并行处理。 - 网络延迟和服务器响应:每个请求都需要通过网络发送到服务器,并等待服务器响应。这个过程中包括网络延迟和服务器处理时间,这些时间都是不可忽略的。
- 线程/进程阻塞:在某些情况下,如果HTTP客户端库或网络栈的实现不是非阻塞的,那么等待响应的过程可能会阻塞当前线程或进程,从而进一步降低程序的并发性能。
解决方案
- 使用异步请求:
- 利用异步编程模型(如Python的
asyncio
、JavaScript的Promises/Async/Await)来同时发送多个HTTP请求,而不需要等待前一个请求完成。
- 利用异步编程模型(如Python的
- 限制并发数:
- 虽然异步请求可以提高效率,但过多的并发请求可能会给服务器或网络带来压力。使用并发限制器(如
aiohttp
的ClientSession
的connector.limit
参数)来控制同时发送的请求数量。
- 虽然异步请求可以提高效率,但过多的并发请求可能会给服务器或网络带来压力。使用并发限制器(如
- 优化网络请求:
- 合并请求:如果可能,将多个请求合并为一个请求,以减少网络往返次数。
- 缓存结果:对于重复请求的数据,使用缓存来避免不必要的网络请求。
- 使用线程或进程池:
- 在不支持异步编程的环境中,可以使用线程池或进程池来并发执行HTTP请求。
结论
在for
循环中连续发送HTTP请求时,如果不加以处理,可能会导致等待时间成倍增加。通过采用异步请求、限制并发数、优化网络请求以及使用线程或进程池等策略,可以有效缓解这一问题,提高程序的执行效率和响应速度。
4.异步并发
import time
import aiohttp
import asyncio
async def fetch_url(url):
print(f"Starting request to {url}")
# 异步:3个请求耗时 3*1 秒
await asyncio.sleep(3) # 模拟网络请求的延时
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["http://example.com", "http://example.org", "http://example.net"]
tasks = [] # 创建一个空列表来存储协程对象
# 使用for循环遍历urls,并为每个URL调用fetch_url函数
# 将返回的协程对象添加到tasks列表中
for url in urls:
task = fetch_url(url)
tasks.append(task)
# 现在所有任务都已经被添加到tasks列表中,我们可以并发地执行它们
results = await asyncio.gather(*tasks)
# 遍历结果并打印
for result, url in zip(results, urls):
print(f"Result for {url}: {result[:50]}...") # 假设我们只打印前50个字符以避免过长输出
# Python 3.7+
asyncio.run(main())
异步编程
异步编程是一种允许程序在等待操作(如I/O操作)完成时继续执行其他任务的编程模型。它通过使用回调函数、Promises、Futures、Async/Await等机制来实现非阻塞的I/O操作。
并发与并行
- 并发:指多个任务可以同时开始执行,但并不意味着它们会同时运行。并发关注的是任务调度的能力,使得多个任务可以在单个或多个处理器上交错执行。
- 并行:指多个任务可以同时运行,即在多个处理器上同时执行。并行是并发的一种形式,但并发不一定要求并行。
异步并发解决HTTP请求问题
异步HTTP请求库
许多现代编程语言都提供了支持异步HTTP请求的库,如Python的aiohttp
、JavaScript的axios
结合async/await
、Node.js的fetch
API等。
实现步骤
- 创建异步函数:使用异步函数(如Python中的
async def
)来封装HTTP请求逻辑。 - 并发执行:使用异步编程的并发控制机制(如Python的
asyncio.gather
)来同时启动多个异步HTTP请求。 - 等待响应:异步等待所有请求完成,并收集响应结果。由于这些请求是并发执行的,因此它们之间的等待时间不会累加。
优点
- 减少等待时间:由于请求是并发执行的,因此总的等待时间大大缩短。
- 提高资源利用率:在等待I/O操作(如网络请求)完成时,程序可以执行其他任务,从而提高CPU和网络资源的利用率。
- 更好的用户体验:在Web应用中,更快的响应时间意味着更好的用户体验。
结论
异步并发是解决for
循环中连续发送HTTP请求导致等待时间成倍增加问题的有效方法。通过采用异步编程模型,我们可以同时处理多个HTTP请求,显著提高程序的执行效率和响应速度。在编写涉及大量网络请求的应用程序时,推荐优先考虑使用异步并发技术。