Python “龙卷风”中的投票工作多长时间?

Python “龙卷风”中的投票工作多长时间?,python,asynchronous,tornado,Python,Asynchronous,Tornado,在Tornado中,它有这样一种方法: @tornado.web.asynchronous def post(self): cursor = self.get_argument("cursor", None) global_message_buffer.wait_for_messages(self.on_new_messages, cursor=cursor) class

在Tornado中,它有这样一种方法:

@tornado.web.asynchronous
def post(self):
    cursor = self.get_argument("cursor", None)
    global_message_buffer.wait_for_messages(self.on_new_messages,
                                            cursor=cursor)
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print("Starting")
from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen
import time

@gen.coroutine
def async_sleep(timeout):
    """ Sleep without blocking the IOLoop. """
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)

class MainHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        print("Start request")
        yield async_sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

if __name__ == "__main__":
    application =  tornado.web.Application([
        (r'/', MainHandler),
    ])
    application.listen(8888)
    IOLoop.instance().start()
我对这种长时间的轮询是相当陌生的,我不太清楚线程是如何工作的,尽管它指出:

通过使用非阻塞网络I/O,Tornado可以扩展到数万个开放连接

我的理论是,通过制作一个简单的应用程序:

import tornado.ioloop
import tornado.web
import time

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print("Start request")
        time.sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

application =  tornado.web.Application([
    (r'/', MainHandler),
])
如果我连续提出两个请求(即打开两个浏览器窗口并快速刷新两个窗口),我会看到:

Start request
Start request
Okay done now
Okay done now
相反,我明白了

Start request
Okay done now
Start request
Okay done now
这让我相信,事实上,在这种情况下,它是阻塞的。为什么我的代码是阻塞的,我如何得到一些代码来实现我的期望?我在Windows7上得到了同样的输出,它有一个核心i7,一个LinuxMint13,我想有两个核心

编辑:
我找到了一种方法——如果有人能提供一种跨平台工作的方法(我不太担心性能,只担心它是非阻塞的),我会接受这个答案。

原始问题中的代码的问题是,当你调用
time.sleep(4)
您有效地阻止了事件循环的执行4秒。被接受的答案也不能解决问题(IMHO)

Tornado中的异步服务基于信任工作。Tornado会在任何情况下调用您的函数,但它相信您会尽快将控制权返回给它。如果您使用
time.sleep()
阻塞,则此信任被破坏-Tornado无法处理新连接

使用多线程只会隐藏错误;使用数千个线程运行Tornado(这样您就可以同时提供1000个连接)将非常低效。适当的方法是运行一个只在Tornado内部阻塞的线程(在
选择
或Tornado侦听事件的任何方式上)-而不是在代码上(确切地说:从不在代码上)

正确的解决方案是在
time.sleep()
(不调用
self.finish()
)之前从
get(self)
返回,如下所示:

@tornado.web.asynchronous
def post(self):
    cursor = self.get_argument("cursor", None)
    global_message_buffer.wait_for_messages(self.on_new_messages,
                                            cursor=cursor)
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print("Starting")
from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen
import time

@gen.coroutine
def async_sleep(timeout):
    """ Sleep without blocking the IOLoop. """
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)

class MainHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        print("Start request")
        yield async_sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

if __name__ == "__main__":
    application =  tornado.web.Application([
        (r'/', MainHandler),
    ])
    application.listen(8888)
    IOLoop.instance().start()
当然,您必须记住,此请求仍处于打开状态,稍后对其调用
write()
finish()


我建议你看看。去掉身份验证后,您将得到一个非常好的异步长轮询服务器示例。

将测试应用程序转换为不会阻止IOLoop的表单的正确方法如下:

@tornado.web.asynchronous
def post(self):
    cursor = self.get_argument("cursor", None)
    global_message_buffer.wait_for_messages(self.on_new_messages,
                                            cursor=cursor)
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print("Starting")
from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen
import time

@gen.coroutine
def async_sleep(timeout):
    """ Sleep without blocking the IOLoop. """
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)

class MainHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        print("Start request")
        yield async_sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

if __name__ == "__main__":
    application =  tornado.web.Application([
        (r'/', MainHandler),
    ])
    application.listen(8888)
    IOLoop.instance().start()

不同之处在于将对
time.sleep
的调用替换为不会阻止IOLoop的调用。Tornado旨在处理大量并发I/O,而不需要多个线程/子进程,但如果使用同步API,它仍然会阻塞。为了让您的长轮询解决方案能够以您喜欢的方式处理并发,您必须确保没有长时间运行的调用阻塞。

自Tornado 5.0以来,异步IO是自动启用的,因此几乎只需将
时间.sleep(4)
更改为
等待异步IO.sleep(4)
@Tornado.web.asynchronous def get(self):
to
async def get(self):
解决了这个问题

例如:

import tornado.ioloop
import tornado.web
import asyncio

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        print("Start request")
        await asyncio.sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

app =  tornado.web.Application([
    (r'/', MainHandler),
])
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
输出:

Start request
Start request
Okay done now
Okay done now
资料来源:


时间。睡眠
会阻止Tornado iLoop,从而停止所有处理。您根本不需要多个线程(尽管您可能希望在生产环境中使用它们),只是不需要睡觉。相反,给IOLoop添加一个超时:请选择dano和john提供的正确答案。我确实看了聊天演示,它的“真实生活”代码比我想要的要多得多,甚至可以删减。我意识到(现在)睡眠不是让它工作的正确方式……不幸的是,这样做仍然会产生“开始”,然后阻塞,直到第一个请求完全完成(至少据我所知)。我同意聊天演示有点臃肿。:)我所做的是,我删除了所有的身份验证内容,留下了一个非常小的应用程序,可以清楚地显示概念(我也将一些函数重命名为-
wait\u-for\u-messages
add\u-to\u-waiters
和类似)。关于第二条评论:不,它没有,这就是它的美。它始终接受新连接,同时保持旧连接打开(直到其他事件(例如另一个请求)关闭它们)。这个例子很好地解释了异步模式。