上面的要点是正确的,从技术上讲,您不能通过用于多路复用 I/O 的阻塞调用来执行此操作,例如 select()、epoll() 和 BSD/iOS、Windows 变体。这些调用允许指定超时,因此您可以通过在短时间内重复轮询来接近,然后将工作传递给主线程之外的异步处理程序。在这种情况下,读取是在主线程上完成的,多次读取可以表明它们已准备就绪,并且主线程主要致力于该任务。
如果您的问题规模小到中等,那么没有什么能比epoll()...read() 甚至select()...read() 更好。如果您的问题(读取通道数)很小。所以我鼓励你考虑一下——从可以专门用于请求的主线程中获得尽可能多的工作。
如果您正在寻找异步解决方案,您最好的选择之一是grequests 库,既易于使用又性能卓越。要获得一个想法,请运行以下客户端-服务器对。请注意,tornado 的使用在这里无关紧要,仅在服务器端,而您关心的是客户端。
试试这个 - 性能差异是白天和黑夜。
下面的 client.py 类代表您的解决方案;它使用grequests 异步发出get() 请求。
server.py
from tornado import (httpserver, options,
ioloop, web, gen)
import time
import ujson as json
from collections import defaultdict
class Check(web.RequestHandler):
@gen.coroutine
def get(self):
try:
data = int(self.get_argument('data'))
except ValueError:
raise web.HTTPError(400, reason='Invalid value for data')
delay = 100
start = time.time()
print('Processed: {!r}'.format(data))
yield gen.Task(ioloop.IOLoop.instance().add_timeout, start + delay / 1000.)
self.write('.')
end = time.time()
self.finish()
if __name__ == '__main__':
port = 4545
application = web.Application([
(r'/get', Check)
])
http_server = httpserver.HTTPServer(application)
http_server.listen(port)
print('Listening on port: {}'.format(port))
ioloop.IOLoop.instance().start()
client.py
import grequests
from tornado.httpclient import HTTPClient
import time
def call_serial(num, httpclient):
url = 'http://127.0.0.1:4545/get?data={}'.format(num)
response = httpclient.fetch(url)
print('Added: {!r}'.format(num))
def call_async(mapper):
futures = (grequests.get(url) for url,_ in mapper)
responses = grequests.map(futures)
for response, (url,num) in zip(responses, mapper):
print('Added: {!r}'.format(num))
def check(num):
if num % 2 == 0:
return False
return True
def serial_calls(httpclient, up_to):
for num in range(up_to):
if check(num):
call_serial(num, httpclient)
def async_calls(httpclient, up_to):
mapper = []
for num in range(up_to):
if check(num):
url = 'http://127.0.0.1:4545/get?data={}'.format(num)
mapper.append((url,num))
call_async(mapper)
if __name__ == '__main__':
httpclient = HTTPClient()
print('SERIAL CALLS')
serial_calls(httpclient, 100)
print('ASYNC CALLS')
async_calls(httpclient, 100)
httpclient.close()
这是一个真正的异步解决方案,或者尽可能接近 CPython/python。没有使用轮询器。