同步 v/s 异步

Synchronous v/s Asynchronous

我正在尝试理解 tornado 文档介绍页面上提供的基本示例。它有 2 个代码块。同步的对我来说很好,我理解它。但是异步的是我看不懂的。

同步

from tornado.httpclient import HTTPClient

def synchronous_fetch(url):
    http_client = HTTPClient()
    response = http_client.fetch(url)
    return response.body

异步

from tornado.httpclient import AsyncHTTPClient

def asynchronous_fetch(url, callback):
    http_client = AsyncHTTPClient()
    def handle_response(response):
        callback(response.body)
    http_client.fetch(url, callback=handle_response)

如果您能提供更好的示例,请提供。

异步调用的想法在许多与网络相关的编程中几乎是一样的..."stuff" (frameworks, servers, libraries...) 不仅仅是来自Tornado web server的概念。

基本思路是:

  • 在 s̲y̲n̲c̲h̲r̲o̲n̲o̲u̲s̲ 请求上,你发出请求并停止执行你的程序,直到你从 HTTP 服务器收到响应(或者如果无法访问服务器则出现错误,或者如果服务器正在占用则超时) ,回复时间太长)解释器 阻塞 直到请求完成(直到你得到关于请求发生的事情的明确答案:进展顺利吗?有错误吗?超时?...)。
  • 在a̲s̲y̲n̲c̲h̲r̲o̲n̲o̲u̲s̲请求上,你"launch"请求,你那种"forget about it",意思是:解释器在请求发出后继续执行代码而不等待请求完成

    这似乎……毫无意义,对吧?你发送请求"to the void of space",照常继续执行?当服务器向您发送响应时会发生什么?我提出了一个请求,我想知道它发生了什么!否则,我一开始就不会在我的代码中输入这个!!

    嗯,这就是 callback 的用武之地。您启动请求 "to the void of space" 但是 您提供一个回调函数,所以当另一端的 HTTP 服务器向您发送响应时,该函数是 运行,第一个参数是 response

让我们举个例子看看。

我创建了一个非常简单的 Tornado 服务器(使用 Python 2.7Tornado 4.2),只有一个处理程序。在 GET 上,到 return 需要 5 秒。我用 time.sleep 做到了这一点,但在现实生活中,这可能是一个非常耗时的过程(访问数据库、执行一些计算……谁知道呢?……)

这是服务器文件(基于 Tornado 文档中提供的example):

SampleServer.py:

#!/usr/bin/env python2.7
import time
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        print "Someone is GET'ing me"
        time.sleep(5)
        self.write("Hello, world")

application = tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    application.listen(8888)
    print "Starting sample server."
    tornado.ioloop.IOLoop.current().start()

打开终端并运行该代码以拥有服务器。您将在本地计算机的端口 8888 上监听 Tornado。

现在,让我们创建另一个脚本(您必须在另一个终端中 运行),该脚本以两种方式 GEThttp://localhost:8888:首先是同步,然后是异步。

SampleFetcher.py:

#!/usr/bin/env python2.7
import datetime
import tornado.ioloop
from tornado.httpclient import HTTPClient, AsyncHTTPClient


def HUMAN_DT_NOW():
    return datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")


def synchronous_fetch(url):
    http_client = HTTPClient()
    start_dt = datetime.datetime.now()
    response = http_client.fetch(url)
    end_dt = datetime.datetime.now()
    print ("The synchronous fetch took %s seconds."
           % (end_dt - start_dt).total_seconds())
    print "(Sync) Server said: \"%s\"" % response.body


def asynchronous_fetch(url):
    http_client = AsyncHTTPClient()

    def handle_response(response):
        print ""
        print "Yawwza... Finally!!!."
        print "The time now is %s" % HUMAN_DT_NOW()
        print "(Async) Server said: \"%s\"" % response.body
    print "Gonna launch a 'fetch' to the universe at %s..." % HUMAN_DT_NOW()
    http_client.fetch(url, callback=handle_response)

if __name__ == "__main__":
    print " ------ Synchronous ------ "
    print ("Starting synchronous fetch at %s."
           " The program will block for about 5 secs." % HUMAN_DT_NOW())
    synchronous_fetch('http://localhost:8888')
    print "Pfew! That was a lot of wait time!!. I got bored watching my terminal"
    print ""
    print "Aight, let's see what Asynchronous can do"
    print " ------ Asynchronous ------ "
    asynchronous_fetch('http://localhost:8888')
    print "You're gonna see this line before the \"Yawwza...\" one"
    print "This one too. Now is %s" % HUMAN_DT_NOW()
    # The IOLoop below is required to prevent the script from closing ahead
    # of time, but allowing Asynchronous interactions
    tornado.ioloop.IOLoop.current().start()

这将输出:

Starting synchronous fetch at 2015/07/04 13:25:47. The program will block for about 5 secs.
The synchronous fetch took 5.009597 seconds.
(Sync) Server said: "Hello, world"
Pfew! That was a lot of wait time!!. I got bored watching my terminal

Aight, let's see what Asynchronous can do
 ------ Asynchronous ------ 
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"

让我们关注异步部分,这里:

 ------ Asynchronous ------ 
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"

如果您看到,脚本在 13:25:52 处生成了 asynchronous_fetch,但是 立即 (在同一秒内),解释器继续执行,并且运行 发出请求后的下一条语句(打印 You're gonna see this line before the "Yawwza..." oneThis one too. Now is 2015/07/04 13:25:52 的行)。

然后,大约 5 秒后,服务器响应,并执行 callback 函数(即 handle_response),这时你会看到

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57

我希望这有助于理解这个想法。这是一个非常有用的概念,它不仅适用于 Tornado。

随意使用提供的两个示例脚本,更改内容,增加服务器回复的次数...

进一步推荐阅读: