7.1 认识异步

1. 同步

我们用两个函数来模拟两个客户端请求,并依次进行处理:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Time: 2020/3/9 11:15
# @Author:zhangmingda
# @File: asynchronization.py
# @Software: PyCharm
# Description:了解异步工作原理
def req_a():
'''模拟请求A'''
print('开始处理请求A')
print('完成处理请求A') def req_b():
'''模拟请求A'''
print('开始处理请求B')
print('完成处理请求B')
def main():
"""模拟tornado框架,处理两个请求"""
req_a()
req_b() if __name__ == "__main__":
main()

执行结果:

D:\Python3_study\tornado1\Scripts\python.exe D:/Python3_study/tornado1/asynchronization.py
开始处理请求A
完成处理请求A
开始处理请求B
完成处理请求B

同步是按部就班的依次执行,始终按照同一个步调执行,上一个步骤未执行完不会执行下一步。

想一想,如果在处理请求req_a时需要执行一个耗时的工作(如IO),其执行过程如何?

import time

def req_a():
'''模拟请求A'''
print('开始处理请求A')
long_io()
print('完成处理请求A') def req_b():
'''模拟请求A'''
print('开始处理请求B')
print('完成处理请求B')
def main():
"""模拟tornado框架,处理两个请求"""
req_a()
req_b()
def long_io():
'''模拟耗时的IO操作'''
print('开始IO操作')
time.sleep(5)
print("完成IO操作")
return "IO Complate!!" if __name__ == "__main__":
main()

执行过程:

D:\Python3_study\tornado1\Scripts\python.exe D:/Python3_study/tornado1/asynchronization.py
开始处理请求A
开始IO操作
完成IO操作
完成处理请求A
开始处理请求B
完成处理请求B

在上面的测试中,我们看到耗时的操作会将代码执行阻塞住,即req_a未处理完req_b是无法执行的。

我们怎么解决耗时操作阻塞代码执行?

2. 异步

对于耗时的过程,我们将其交给别人(如其另外一个线程)去执行,而我们继续往下处理,当别人执行完耗时操作后再将结果反馈给我们,这就是我们所说的异步。

我们用容易理解的线程机制来实现异步。

2.1 回调写法实现原理

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# @Time: 2020/3/15 15:10
# @Author:zhangmingda
# @File: asynchronization.py
# @Software: PyCharm
# Description:了解异步工作原理
import time
import threading def long_io():
'''模拟耗时的IO顺序执行操作'''
print('开始IO操作')
time.sleep(5)
print("完成IO操作")
return "IO Complate!!" def long_io_async(callback):
'''函数方式模拟异步IO:将耗时操作交给另一个线程来处理'''
def fun(cb):
'''模拟耗时的IO操作睡眠5秒,'''
print('开始IO操作')
time.sleep(5)
print("完成IO操作")
cb("IO Complate!!")
#这里新启动了一个线程
t1 = threading.Thread(target=fun,args=(callback,))
t1.start()
class MyLongIOThread(threading.Thread):
'''
用类的方式模拟异步IO,启动一个新的线程模拟IO操作
'''
def __init__(self,callback):
super(MyLongIOThread,self).__init__()
self.callback = callback
def run(self):
'''模拟耗时的IO操作睡眠5秒,'''
print('开始IO操作')
time.sleep(5)
print("完成IO操作")
#开始执行回调函数,传递文本()给回调函数
self.callback("IO Complate!!") def on_finish(result):
'''回调函数'''
print('开始执行回调函数on_finish')
print('result: %s' % result)
print('完成回调函数on_finish') def req_a():
'''模拟请求A'''
print('开始处理请求A')
# io_resu = long_io() #测试顺序执行的效果
long_io_async(on_finish)
logio = MyLongIOThread(on_finish)
logio.start()
print('离开处理请求A') def req_b():
'''模拟请求A'''
print('开始处理请求B')
print('完成处理请求B')
def main():
"""模拟tornado框架,处理两个请求"""
req_a()
req_b() if __name__ == "__main__":
main()

执行过程:

D:\Python3_study\tornado1\Scripts\python.exe D:/Python3_study/tornado1/asynchronization.py
开始处理请求A
开始IO操作
开始IO操作
离开处理请求A
开始处理请求B
完成处理请求B
完成IO操作
完成IO操作
开始执行回调函数on_finish
result: IO Complate!!
完成回调函数on_finish
开始执行回调函数on_finish
result: IO Complate!!
完成回调函数on_finish


Process finished with exit code 0


异步的特点是程序存在多个步调,即本属于同一个过程的代码可能在不同的步调上同时执行。

2.2 协程写法实现原理

在使用回调函数写异步程序时,需将本属于一个执行逻辑(处理请求a)的代码拆分成两个函数req_a和on_finish,这与同步程序的写法相差很大。而同步程序更便于理解业务逻辑,所以我们能否用同步代码的写法来编写异步程序?

回想yield关键字的作用?

初始版本

#!/usr/bin/env python2
# -*- coding:utf-8 -*-
# @Time: 2020/3/15 15:21
# @Author:zhangmingda
# @File: yieldsaynchronization.py
# @Software: PyCharm
# Description:yield 模仿协程第一个版本 # coding:utf-8 import time
import threading gen = None # 全局生成器,给MyLongIO使用 class MyLongIO(threading.Thread):
def __init__(self):
super(MyLongIO,self).__init__()
def run(self):
print('开始执行IO操作,需要5秒')
global gen
time.sleep(5)
try:
print('完成IO操作,并send结果唤醒挂起程序继续执行')
gen.send("io result") # 使用send返回结果并唤醒程序继续执行
except StopIteration: #捕获生成器完成迭代,防止程序退出
pass def req_a():
print("开始处理请求req_a")
longio = MyLongIO()
ret = yield longio.start() print("ret: %s" % ret)
print("完成处理请求req_a") def req_b():
print("开始处理请求req_b")
time.sleep(2)
print("完成处理请求req_b") def main():
global gen
gen = req_a()
gen.next() # 开启生成器req_a的执行
req_b()
while 1:
pass if __name__ == '__main__':
main()

执行过程:

(base) [root@zmdsdkhost ~]# python2 yieldtest.py
开始处理请求req_a
开始执行IO操作,需要5秒
开始处理请求req_b
完成处理请求req_b
完成IO操作,并send结果唤醒挂起程序继续执行
ret: io result
完成处理请求req_a

升级版本

我们在上面编写出的版本虽然req_a的编写方式很类似与同步代码,但是在main中调用req_a的时候却不能将其简单的视为普通函数,而是需要作为生成器对待。

现在,我们试图尝试修改,让req_a与main的编写都类似与同步代码。

#!/usr/bin/env python2
# coding:utf-8 import time
import thread gen = None # 全局生成器,供long_io使用 def gen_coroutine(f):
def wrapper(*args, **kwargs):
global gen
gen = f()
gen.next()
return wrapper def long_io():
def fun():
print "开始执行IO操作"
global gen
time.sleep(5)
try:
print "完成IO操作,并send结果唤醒挂起程序继续执行"
gen.send("io result") # 使用send返回结果并唤醒程序继续执行
except StopIteration: # 捕获生成器完成迭代,防止程序退出
pass
thread.start_new_thread(fun, ()) @gen_coroutine
def req_a():
print "开始处理请求req_a"
ret = yield long_io()
print "ret: %s" % ret
print "完成处理请求req_a" def req_b():
print "开始处理请求req_b"
time.sleep(2)
print "完成处理请求req_b" def main():
req_a()
req_b()
while 1:
pass if __name__ == '__main__':
main()

执行过程:

开始处理请求req_a
开始处理请求req_b
开始执行IO操作
完成处理请求req_b
完成IO操作,并send结果唤醒挂起程序继续执行
ret: io result
完成处理请求req_a

最终版本

刚刚完成的版本依然不理想,因为存在一个全局变量gen来供long_io使用。我们现在再次改写程序,消除全局变量gen。

# coding:utf-8

import time
import thread def gen_coroutine(f):
def wrapper(*args, **kwargs):
gen_f = f() # gen_f为生成器req_a
r = gen_f.next() # r为生成器long_io
def fun(g):
ret = g.next() # 执行生成器long_io
try:
gen_f.send(ret) # 将结果返回给req_a并使其继续执行
except StopIteration:
pass
thread.start_new_thread(fun, (r,))
return wrapper def long_io():
print "开始执行IO操作"
time.sleep(5)
print "完成IO操作,yield回操作结果"
yield "io result" @gen_coroutine
def req_a():
print "开始处理请求req_a"
ret = yield long_io()
print "ret: %s" % ret
print "完成处理请求req_a" def req_b():
print "开始处理请求req_b"
time.sleep(2)
print "完成处理请求req_b" def main():
req_a()
req_b()
while 1:
pass if __name__ == '__main__':
main()

执行过程:

开始处理请求req_a
开始处理请求req_b
开始执行IO操作
完成处理请求req_b
完成IO操作,yield回操作结果
ret: io result
完成处理请求req_a

这个最终版本就是理解Tornado异步编程原理的最简易模型,但是,Tornado实现异步的机制不是线程,而是epoll,即将异步过程交给epoll执行并进行监视回调。

需要注意的一点是,我们实现的版本严格意义上来说不能算是协程,因为两个程序的挂起与唤醒是在两个线程上实现的,而Tornado利用epoll来实现异步,程序的挂起与唤醒始终在一个线程上,由Tornado自己来调度,属于真正意义上的协程。虽如此,并不妨碍我们理解Tornado异步编程的原理。

思考

  1. Tornado里的异步就是协程,这句话对吗?
  2. Tornado中出现yield就是异步,这句话对吗?
  3. 怎么理解yield将程序挂起?在Tornado中又如何理解yield挂起程序实现异步?

Tornado 异步浅解的更多相关文章

  1. 使用Tornado异步接入第三方(支付宝)支付

    目前国内比较流行的第三方支付主要有支付宝和微信支付,博主最近研究了下如何用Python接入支付宝支付,这里我以Tornado作为web框架,接入支付宝构造支付接口. 使用Tornado异步接入支付宝支 ...

  2. 5.(基础)tornado异步

    终于到了传说中的异步了,感觉异步这个名字听起来就很酷酷的,以前还不是多擅长Python时,就跑去看twisted的源码,结果给我幼小的心灵留下了创伤.反正包括我在内,都知道异步编程很强大,但是却很少在 ...

  3. 触碰jQuery:AJAX异步详解

    触碰jQuery:AJAX异步详解 传送门:异步编程系列目录…… 示例源码:触碰jQuery:AJAX异步详解.rar AJAX 全称 Asynchronous JavaScript and XML( ...

  4. jQuery调用AJAX异步详解[转]

    AJAX 全称 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).它并非一种新的技术,而是以下几种原有技术的结合体. 1)   使用CSS和X ...

  5. tornado异步请求的理解(转)

    tornado异步请求的理解 http://www.kankanews.com/ICkengine/archives/88953.shtml 官网第一段话: Tornado is a Python w ...

  6. 触碰jQuery:AJAX异步详解(转)

    AJAX 全称 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).它并非一种新的技术,而是以下几种原有技术的结合体. 1)   使用CSS和X ...

  7. 从最大似然到EM算法浅解

    从最大似然到EM算法浅解 zouxy09@qq.com http://blog.csdn.net/zouxy09 机器学习十大算法之中的一个:EM算法.能评得上十大之中的一个,让人听起来认为挺NB的. ...

  8. Tornado异步非阻塞的使用以及原理

    Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快.得利于其 非阻塞的方式和对 epoll 的运用,Tornado ...

  9. Python Web框架 tornado 异步原理

    Python Web框架 tornado 异步原理 参考:http://www.jb51.net/article/64747.htm 待整理

随机推荐

  1. 强化学习之MountainCarContinuous(注册自己的gym环境)

    目录 1. 问题概述 2. 环境 2.1 Observation & state 2.2 Actions 2.3 Reward 2.4 初始状态 2.5 终止状态- Episode Termi ...

  2. setoolkit的钓鱼实验

    1.在kali中打开setoolkit 2.在菜单中选择第一个进入社会工程学攻击 3.选择第二个模块属于网站攻击向量 4.选择第五个模块,进行web劫持攻击 5.选择第二个,进行网站克隆 6.发现访问 ...

  3. Geotools核心特点以及支持数据的格式和标准

    Geotools是一个java类库,它提供了很多的标准类和方法来处理空间数据,同时这个类库是构建在OGC标准之上的,是OGC思想的一种实现.而OGC是国际标准,所以geotools将来必定会成为开源空 ...

  4. OI省选算法汇总及学习计划(转)

    1.1 基本数据结构 数组(√) 链表(√),双向链表(√) 队列(√),单调队列(√),双端队列(√) 栈(√),单调栈(√) 1.2 中级数据结构 堆(√) 并查集与带权并查集(√) hash 表 ...

  5. 【豆科基因组】大豆适应性位点GWAS分析 [转载]

    目录 材料与方法 结果分析 本文利用99085个高质量SNP 通过STRUCTURE,PCA和neighbour-joining tree的群体结构分析将地方品种分为三个亚群,这些亚群表现出地理上的遗 ...

  6. nginx_access_log的格式设置

    log_format <NAME> <Strin­­­g>; 关键字 格式标签 日志格式 关键字:其中关键字error_log不能改变 格式标签:格式标签是给一套日志格式设置一 ...

  7. Linux—su命令和su -命令的差别(切换登录账号)

    1.普通用户切换到root用户,命令su或su - 本人以前一直习惯直接使用root,很少使用su,前几天才发现su与su -命令是有着本质区别的! 大部分Linux发行版的默认账户是普通用户,而更改 ...

  8. sersync+rsync进行数据同步

    一:环境 操作系统环境:redhat6.6 内核版本:2.6.32-358.el6.x86_64 rsync server:192.168.2.3(部署rsync server) rsync clie ...

  9. sqlalchemy模块的基本使用

    Python中SQLAlchemy模块通过建立orm来对数据库进行操作 1. 建表 方式1 # -*- coding:utf-8 -*- # Author:Wong Du from sqlalchem ...

  10. 出现NoClassDefFoundError,始终无法引入jar的解决

    在拉取代码后,项目的部分版本与本地存在的不一定一致,所以IDEA会自动下载并引入,但是在启动时可能存在java.lang.NoClassDefFoundError这个报错 比如引入marshallin ...