其他
一篇文章搞懂「Python异步编程」的基本原理
The following article is from 未闻Code Author kingname
点击上方“Python数据科学”,星标公众号
重磅干货,第一时间送达
未闻 Code
已经发布过很多篇关于异步爬虫与异步编程的文章,最近有读者希望我能深入介绍一下 asyncio 是如何通过单线程单进程实现并发效果的。以及异步
代码是不是能在所有方面都代替同步代码。一些例子
第一个例子
先给朋友打电话,让他现在出门 把衣服放进洗衣机并打开电源 把米淘洗干净,放进电饭煲并打开电源
第二个例子
第三个例子
把衣服放进洗衣机并打开电源 把米淘洗干净,放进电饭煲并打开电源 开始完成试卷
能异步与不能异步
异步只适用于 I/O 操作相关的代码,不适用于非 I/O操作。
Python 的异步代码
a = 1 + 1
b = 2 + 2
c = 3 + 3
拿到返回的数据,做其他事情
http://127.0.0.1:8000/sleep/<num>
时,网站会等待num
秒才会返回。例如:http://127.0.0.1:8000/sleep/3
表示,当你发起请求后,网站会等待3秒钟再返回。运行效果如下图所示。import asyncio
import time
async def request(sleep_time):
async with aiohttp.ClientSession() as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{sleep_time}')
resp_json = await resp.json()
print(resp_json)
async def main():
start = time.perf_counter()
await request(1)
a = 1 + 1
b = 2 + 2
print('能不能在第一个请求等待的过程中运行到这里?')
await request(2)
print('能不能在第二个请求等待的过程中运行到这里?')
await request(3)
end = time.perf_counter()
print(f'总计耗时:{end - start}')
asyncio.run(main())
能不能在第二个请求等待的过程中运行到这里?
{'success': True, 'time': 1}
{'success': True, 'time': 2}
{'success': True, 'time': 3}
总计耗时:3.018130547
await
语句告诉 asyncio,它后面这个函数,可以被异步等待。注意是可以被
等待,但要不要等待,这是 Python 底层自己来决定的。import asyncio
import time
async def request(sleep_time):
async with aiohttp.ClientSession() as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{sleep_time}')
resp_json = await resp.json()
print(resp_json)
async def main():
start = time.perf_counter()
tasks_list = [
asyncio.create_task(request(1)),
asyncio.create_task(request(2)),
asyncio.create_task(request(3)),
]
await asyncio.gather(*tasks_list)
end = time.perf_counter()
print(f'总计耗时:{end - start}')
asyncio.run(main())
asyncio.create_task()
把不同的协程定义成异步任务,并把这些异步任务放入一个列表中,凑够一批任务以后,一次性提交给asyncio.gather()
。于是,Python 就会自动调度这一批异步任务,充分利用他们的请求等待时间发起新的请求。yield scrapy.Request(url, callback=self.parse)
next_url = url + '&page=2'
yield scrapy.Request(next_url, callback=self.parse)
next_url = url + '&page=2'
接下来再发起另一个请求。yield scrapy.Request
后, 仅仅是把一个请求对象放入 Scrapy 的请求队列里面,然后就继续执行next_url = url + '&page=2'
了。在异步代码中调用同步函数
print
函数就是一个同步函数,但是由于它耗时极短,所以不会卡住异步任务。if n in [1, 2]:
return 1
return sync_calc_fib(n - 1) + sync_calc_fib(n - 2)
async def calc_fib(n):
result = sync_calc_fib(n)
print(f'第 {n} 项计算完成,结果是:{result}')
return result
time.sleep
的原因。