第7节,tornado异步编程理论基础---什么是阻塞,什么是异步

1. 什么是阻塞

在学习tornado 如何编写异步代码之前,首先需要了解什么是异步和阻塞,这两个概念联系十分紧密且常常交换使用,但他们并不完全相同。

在讨论阻塞时,必须先设定场景,阻塞这个概念,必须放在单个线程内讨论才能真正理解其含义。

当你调用一个函数时,暂且不关心函数内做了什么,在函数返回结果之前,整个程序阻塞在函数调用的这行代码上,任何其他语句都不能被执行。

def do_something():
    pass
    
do_something()
# 只有等到do_something执行结束,后面的代码才能被执行。

函数的执行需要时间,有时很长,有时很短,被阻塞的原因也会不一样,可能是磁盘IO,网络IO,也可能占用CPU做很重的计算。

我们所编写的web程序,不论你采用多进程部署还是多线程部署,从单个线程的视角来看,一旦遇到一个阻塞时间很长的函数,那么这个线程都不能再处理其他请求。

2. 异步

异步函数在执行结束前就返回了,通常在程序里触发一些动作并在后台做一些任务。这里要注意一点,所谓结束前就返回了,并不是返回了最终结果,异步函数触发了获取最终结果的动作,等到最终结果出现时再去处理最终结果。

下面是几种类型的异步接口:

  1. 回调函数
  2. 返回一个占位符 (Future, Promise, Deferred)
  3. 传送一个队列
  4. 回调注册 (例如. POSIX 信号)

下面用最简单的例子来做说明

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)

asynchronous_fetch 函数向url发起网络请求,假设这个请求需要1秒钟的时间,如果是同步函数,那么这个函数执行时长最少为1秒,但使用异步函数,则是瞬间完成执行。

但asynchronous_fetch函数内触发了请求url 的这个动作,只是在asynchronous_fetch内并不等待结果的返回,这就是http_client.fetch(url, callback=handle_response) 所做的事情。1秒钟以后,程序一定会得到请求url的结果,但此时asynchronous_fetch早在1秒钟之前就结束了,谁来处理这个最终结果呢?答案是callback。

在调用asynchronous_fetch函数时,除了url参数外,还要指定callback参数,必须为它传入一个函数,专门用来处理response.body,现在,我们来梳理一下整个异步过程:

  1. asynchronous_fetch 被调用,触发http_client的fetch方法向url发起请求,随后asynchronous_fetch函数执行结束,几乎不耗费时间
  2. fetch方法在后台(不阻塞程序)执行对url的请求
  3. 1秒钟以后,得到了url的返回结果,此时调用handle_response函数处理相应结果response
  4. handle_response 内部调用callback 处理response.body

这里你必须明确并坚信一点,在第2步里,fetch方法在进行网络IO时,不会阻塞程序,因此asynchronous_fetch也不会阻塞程序,于是乎在调用完asynchronous_fetch函数后,你可以立即做其他事情,1秒钟以后再去处理请求的结果。

这便是关于异步的理解,想要更深入的理解异步,你必须深入到socket层面去学习理解select 和 epoll 模型,从根本上讲,IO多路复用是网络异步编程的基础,这里不做展开。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案