异步爬虫
爬虫是IO密集型任务
,在发出一个请求之后,程序必须等到网站返回响应,程序才能接着运行,如果等待的时间很长的话,爬虫程序也会在一直在等待,什么是也不做,就会浪费时间。
异步爬虫可以解决这个问题。
点击直接看异步爬虫的实现
文章源地址:修能的博客
协程的基本原理
要实现异步爬虫,一个重点就是协程
(coroutine
)
示例
来看看这个网站:https://www.httpbin.org/delay/5
,这个连接需要先等待5秒钟才能得到结果,因为这时服务器强制得到5秒才能响应。
如果用requests库
来访问这个网站10次,来查看相应的时间。
import requests
import logging
import time
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s : %(message)s')
TOTAL_NUMBER = 10
URL = 'https://www.httpbin.org/delay/5'
start_time = time.time()
for _ in range(1,TOTAL_NUMBER+1):
logging.info('sraping %s',URL)
response = requests.get(URL)
end_time = time.time()
logging.info('total time %s second',end_time - start_time)
普通使用的requests.get()
是单线程,在爬取之前先记录时间,爬取之后再记录一次时间,最后输出总时间。
2023-07-18 19:19:00,051 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:19:11,939 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:19:25,818 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:19:36,742 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:19:46,669 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:19:53,518 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:20:53,680 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:21:06,351 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:21:17,378 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:21:25,885 - INFO : sraping https://www.httpbin.org/delay/5
2023-07-18 19:22:15,833 - INFO : total time 195.7818102836609 second
可以看到爬取就用了195秒的时间。
要是用多进程或多线程来爬取,时间会减短很多。
协程基础知识
-
阻塞
阻塞状态是指程序未得到所需的计算资源时被挂起的状态。
程序在完成某个操作完成之前,自身是不能完成别的任务。
-
非阻塞
非阻塞状态是指程序在等待某操作的时候,本身不被阻塞,可以干其他的事情,则这个程序是非阻塞的。
-
同步
不同程序单元为了共同完成某个任务,在执行过程中需要靠某种通信方式保持协调一致
此时这些程序单元是同步执行的。
-
异步
为了完成某个任务,有时不同的程序单元之间无须通信协调也能完成任务。
异步意味着无序。
-
多进程
就是利用处理器的多核优势,在同一时间并行执行多个任务,大大提高执行的效率。
-
协程
一种运行在
用户态
的轻量级线程协程拥有自己的寄存器上下文和栈。
协程在调度切换的时候,将寄存器上下文和栈保存到其他地方,等切回来是在恢复先前保存的寄存器上下文和栈。因此,协程能够保留上一次调用时的状态。
协程的本质是一个单线程,相对于多线程来说,它没有上下文切换的开销,没有
原子操作锁定
及同步
的开销,编程模型也比较简单。原子操作锁定(Atomic Operation Locking)是一种同步机制,用于确保在多线程或并发环境下对共享资源的原子性访问。原子操作是指在执行过程中不可中断的操作,要么完全执行,要么完全不执行,不存在部分执行的情况。
使用原子操作锁定可以解决并发环境下的竞争条件问题,确保共享资源的正确访问。它通常用于对变量进行读取、写入或修改操作,以确保线程的操作不会相互干扰或产生意外结果。
在编程中,原子操作锁定通常由特殊的锁或原子操作指令来实现。这些机制可以保证一个线程在执行原子操作时不会被其他线程中断或干扰。常见的原子操作锁定机制包括互斥锁(Mutex)、自旋锁(Spin Lock)以及原子操作指令(Atomic Instructions)等。
协程的用法
从Python3.4
开始,Python中加入了协程的概念。
在Python3.5
中有增加了async
和await
,使得协程到的实现更为方便。
asyncio库
是Python协程最常用的库。
想要学好协程,必须了解一下的概念:
-
event_loop
:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足发生条件是就会调用对应的处理方法。 -
coroutine
:协程,Python中常指代协程对象类型
,把协程对象注册到是事件循环中,他会把事件循环调用。使用
async
关键字来定义一个方法,这个方法在调用时不会立即执行,而是会返回一个协程对象
。 -
task
:任务,这是对协程对象的进一步包装,包含协程对象的各个状态。 -
future
:代表将要执行或者没用执行的任务的结果,实际上跟task
没有本质区别。
准备工作
要求Python3.5及以上
。
定义协程
import asyncio
async def execute(x):
print('Number:', x)
coroutine = execute(1)
print('Coroutine:', coroutine)
print('After calling execute')
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('After calling loop')
Coroutine: <coroutine object execute at 0x000001E51B12C110>
After calling execute
Number: 1
After calling loop
D:\PythonProject\异步爬虫\定义协程.py:12: DeprecationWarning: There is no current event loop
loop = asyncio.get_event_loop()
防止DeprecationWarning: There is no current event loop警报,请这样写:
loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) asyncio.ensure_future(task,loop=loop)
首先引入asyncio
包,这样才可以使用async
和await
关键词。
然后使用async
定义了一个execute
方法,传入数字参数,执行会打印这个数字。
然后执行execute
方法,然而程序并没有执行程序,而是返回了一个coroutine
协程对象,之后我们使用get_event_complete
方法来创建一个事件循环loop
,再调用run_until_complete
将协程对象注册到事件循环,接着启动程序,就会打印数字了。
可见,协程对象只能在被注册到事件循环中之后才会执行。
其实在把协程对象coroutine
传递到run_until_complete
方法的时候,这里隐式的将coroutine
对象封装成task
对象,我们也可以显示声明。
import asyncio
async def execute(x):
print('Number:', x)
coroutine = execute(1)
print('Coroutine:', coroutine)
print('After calling execute')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
print('Task:', task)
loop.run_until_complete(task)
print('Task', task)
print('After calling loop')
Coroutine: <coroutine object execute at 0x00000164A373C110>
After calling execute
Task: <Task pending name='Task-1' coro=<execute() running at D:\PythonProject\异步爬虫\定义协程.py:4>>
Number: 1
Task <Task finished name='Task-1' coro=<execute() done, defined at D:\PythonProject\异步爬虫\定义协程.py:4> result=None>
After calling loop
D:\PythonProject\异步爬虫\定义协程.py:12: DeprecationWarning: There is no current event loop
loop = asyncio.get_event_loop()
会看到在没有执行的时候,task的状态时pending
,执行完之后就是finished
。
asyncio.ensure_future
方法,返回的对象也是task
对象,这样可以不借助loop
对象也可以构造task
对象。
import asyncio
async def execute(x):
print('Number:', x)
coroutine = execute(1)
print('Coroutine:', coroutine)
print('After calling execute')
task = asyncio.ensure_future(coroutine)
print('Task:', task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task', task)
print('After calling loop')
After calling execute
Task: <Task pending name='Task-1' coro=<execute() running at D:\PythonProject\异步爬虫\定义协程.py:4>>
Number: 1
Task <Task finished name='Task-1' coro=<execute() done, defined at D:\PythonProject\异步爬虫\定义协程.py:4> result=None>
After calling loop
绑定回调
task
对象可以绑定一个回调方法
回调(Callback)是一种常见的编程模式,用于在异步操作完成后通知调用方或执行额外的操作。它是一种通过函数或对象来实现的机制,用于处理异步操作的结果。
在回调模式中,当执行一个异步操作时,可以提供一个回调函数或回调对象作为参数。当异步操作完成时,系统会调用回调函数或回调对象的特定方法,并通过参数传递操作的结果或错误信息。
回调模式的优点是它可以允许程序在进行异步操作时不需要等待结果,而是通过回调函数异步地接收结果,并在结果可用时进行处理。这样可以避免阻塞主线程或导致长时间的等待。
import asyncio
import requests
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
async def request():
url = 'https://www.helloxiunneg.com.cn'
status = requests.get(url).status_code
return status
def callback(task):
print('Status:', task.result())
coroutine = request()
task = asyncio.ensure_future(coroutine, loop=loop)
task.add_done_callback(callback)
print('Task:', task)
loop.run_until_complete(task)
print('Task:', task)
Task: <Task pending name='Task-1' coro=<request() running at D:\PythonProject\异步爬虫\绑定回调.py:8> cb=[callback() at D:\PythonProject\异步爬虫\绑定回调.py:14]>
Status: 200
Task: <Task finished name='Task-1' coro=<request() done, defined at D:\PythonProject\异步爬虫\绑定回调.py:8> result=200>
首先定义协程对象request
,函数会请求博客网址,获得请求状态码,callback
方法,方法接受一个task
对象,在这个方法中打印出task
对象的结果。
两者怎么关联呢?
用add_done_callback
方法将callback
方法传递给封装好的task
对象,每当task
对象执行完毕后就可以调用callback
方法了。
多任务协程
如果想多次执行请求怎么办?
可以定义一个task
列表,然后使用asyncio
的wait
方法,再注册到事件循环中就行了。
import asyncio
import requests
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
async def request():
url = 'https://www.helloxiunneg.com.cn'
status = requests.get(url).status_code
return status
tasks = [asyncio.ensure_future(request(), loop=loop) for _ in range(5)]
print('Task:', tasks)
loop.run_until_complete(asyncio.wait(tasks))
print('Task', tasks)
for task in tasks:
print('Rusult:',task.result())
Task: [<Task pending name='Task-1' coro=<request() running at D:\PythonProject\异步爬虫\多任务协程.py:8>>, <Task pending name='Task-2' coro=<request() running at D:\PythonProject\异步爬虫\多任务协程.py:8>>, <Task pending name='Task-3' coro=<request() running at D:\PythonProject\异步爬虫\多任务协程.py:8>>, <Task pending name='Task-4' coro=<request() running at D:\PythonProject\异步爬虫\多任务协程.py:8>>, <Task pending name='Task-5' coro=<request() running at D:\PythonProject\异步爬虫\多任务协程.py:8>>]
Task [<Task finished name='Task-1' coro=<request() done, defined at D:\PythonProject\异步爬虫\多任务协程.py:8> result=200>, <Task finished name='Task-2' coro=<request() done, defined at D:\PythonProject\异步爬虫\多任务协程.py:8> result=200>, <Task finished name='Task-3' coro=<request() done, defined at D:\PythonProject\异步爬虫\多任务协程.py:8> result=200>, <Task finished name='Task-4' coro=<request() done, defined at D:\PythonProject\异步爬虫\多任务协程.py:8> result=200>, <Task finished name='Task-5' coro=<request() done, defined at D:\PythonProject\异步爬虫\多任务协程.py:8> result=200>]
Rusult: 200
Rusult: 200
Rusult: 200
Rusult: 200
Rusult: 200
协程实现
知道如何编写协程的代码了,我们再来实现以下对强制请求事件的网站,看看协程编程的用处。
注意这是不完全版本!
import asyncio
import requests
import logging
import time
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s : %(message)s')
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
TOTAL_NUMBER = 10
URL = 'https://www.httpbin.org/delay/5'
start_time = time.time()
async def request():
print('Waiting for,', URL)
response = requests.get(URL)
print('Get response from', URL, 'response', response)
tasks = [asyncio.ensure_future(request(), loop=loop) for _ in range(TOTAL_NUMBER)]
loop.run_until_complete(asyncio.wait(tasks))
end_time = time.time()
logging.info('total time %s second', end_time - start_time)
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [504]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [504]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [504]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
Waiting for, https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response <Response [200]>
2023-07-18 21:30:35,390 - INFO : total time 290.7061789035797 second
可以看到事件并没有加速,反而还更慢了!!!
其实要实现异步处理,先得有挂起
操作
当一个任务需要等待IO结果
时,可以挂起当前任务,转而进行其他任务这样才能更好的利用资源。
要实现异步,就要用await
关键词,它可以将耗时等待的操作挂起,让出控制权。
如果协程再执行的时候遇到了await
,事件循环就会将本协程挂起,转去执行其他的协程,知道协程挂起或者选择完毕。
改写代码如下:
async def request():
print('Waiting for,', URL)
response = requests.get(URL)
print('Get response from', URL, 'response', response)
2023-07-18 21:46:14,566 - ERROR : Task exception was never retrieved
future: <Task finished name='Task-1' coro=<request() done, defined at D:\PythonProject\异步爬虫\协程延时访问.py:18> exception=TypeError("object Response can't be used in 'await' expression")>
Traceback (most recent call last):
File "D:\PythonProject\异步爬虫\协程延时访问.py", line 20, in request
response = await requests.get(URL)
^^^^^^^^^^^^^^^^^^^^^^^
但是这次却发生了错误,意思是requests
返回的Response
对象不能和await
一起使用。
await
在官方文档中,await
后面的对象必须是以下格式之一:
-
一个原生协程对象
原生协程对象(Native Coroutine Object)是指使用
async
关键字定义的协程函数生成的协程对象。在Python中,可以使用
async def
语法定义一个原生协程函数。原生协程函数可以包含await
关键字来等待其他协程完成。当调用原生协程函数时,它将返回一个原生协程对象。 -
一个由
types.coroutine
修饰的生成器,这个生成器可以返回协程对象types.coroutine
是一个装饰器函数,用于将生成器函数转换为原生协程对象。在Python中,生成器函数是一种特殊类型的函数,使用yield
关键字来定义迭代器对象。使用
types.coroutine
装饰器可以将生成器函数转换为原生协程对象,使其可以像其他原生协程函数一样被调度执行。 -
由一个包含
__await__
特殊方法的对象返回的迭代器包含
__await__
特殊方法的对象返回的迭代器在Python中被称为可迭代的等待对象(awaitable object)。可迭代的等待对象是那些实现了
__await__
方法的对象。这个方法必须返回一个迭代器对象,该迭代器对象会在被await时被调用。通常情况下,可迭代的等待对象可以是原生协程对象或异步上下文管理器对象。
所以只要将返回的结果改成原生协程对象就可以了吗?
试一下!
async def get(URL): return requests.get(URL) async def request(): print('Waiting for,', URL) response = await get(URL) print('Get response from', URL, 'response', response
Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [504]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [504]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [504]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> Waiting for, https://www.httpbin.org/delay/5 Get response from https://www.httpbin.org/delay/5 response <Response [200]> total time %s second 133.18833374977112
可以看到还是没有实现异步操作,所以仅仅将
IO
操作封装到async修饰的方法还是不行的。只有支持异步操作的请求方法财可以实现真正的异步。
使用aiohttp
aiohttp
库是一个支持异步请求的库,它和asycnio
库配合使用,可以使我们非常方便地实现异步请求操作。
aiohttp
的官方文档链接为:(https://aiohhtp.readthedocs.io/)
它分为两个部分,一部分为Client,另一部分时Server。
安装aiohttp
终端输入pip install aiohttp
即可。
请确保网络通畅!
改写代码
import asyncio
import aiohttp
import time
TOTAL_NUMBER = 10
start = time.time()
async def get(url):
session = aiohttp.ClientSession()
response = await session.get(url)
await response.text()
await session.close()
return response
async def request():
url = 'https://www.httpbin.org/delay/5'
print('Waiting for', url)
response = await get(url)
print('Get responses from', url, 'response', response)
tasks = [asyncio.ensure_future(request()) for _ in range(TOTAL_NUMBER)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print('Cost Time:', end - start)
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:50 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:51 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:52 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:53 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [504 Gateway Time-out]>
<CIMultiDictProxy('Server': 'awselb/2.0', 'Date': 'Tue, 18 Jul 2023 15:30:53 GMT', 'Content-Type': 'text/html', 'Content-Length': '132', 'Connection': 'keep-alive')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:54 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:30:54 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:31:15 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:31:19 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Get responses from https://www.httpbin.org/delay/5 response <ClientResponse(https://www.httpbin.org/delay/5) [200 OK]>
<CIMultiDictProxy('Date': 'Tue, 18 Jul 2023 15:31:29 GMT', 'Content-Type': 'application/json', 'Content-Length': '369', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
Cost Time: 47.753435373306274
可以看到真正的异步请求在时间效率上远远高于单线程的请求。
原理解析
开始运行时,时间循环会运行第一个task
,对于第一个task
来说,当执行到第一个await
跟着的get
方法时,它会被挂起,但这个方法第一步时非阻塞的,挂起后会立马唤醒,立即又进行执行,并创建了ClientSession
对象。
接着又遇到了第二个await
,调用session.get
方法,又被挂起,但是由于请求的时间很长,第一个被挂起的请求还处于被挂起状态,之后怎么办呢?
事件循环会寻找当前未被挂起的协程继续运行,以此类推。
那么当所有的task
都被挂起了怎么办?
只能等待了,当排在前面的请求响应后,之后的task
会被唤醒,以此类推。
总结
文章源地址:修能的博客
异步操作的便捷之处就是,当遇到阻塞型操作时,task被挂起,程序接着去处理其他协程,充分的利用CPU,提高时间效率。
但是响应的处理时间的影响因素不只在于程序是由是异步还是同步,还在于服务器的处理能力,服务器处理高并发的任务的能力也是影响程序请求时间的重要因素。