关于Tornado5.1:到底是真实的异步和还是虚假的异步
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_107
我们知道Tornado 优秀的大并发处理能力得益于它的 web server 从底层开始就自己实现了一整套基于 epoll 的单线程异步架构,其他 web 框架比如Django或者Flask的自带 server 基本是基于 wsgi 写的简单服务器,并没有自己实现底层结构。而tornado.ioloop 就是 tornado web server 最底层的实现。
ioloop 的实现基于 epoll ,那么什么是 epoll? epoll 是Linux内核为处理大批量文件描述符而作了改进的 poll / select 。
那么到底什么是 poll / select ? socket 通信时的服务端,当它接受( accept )一个连接并建立通信后( connection )就进行通信,而此时我们并不知道连接的客户端有没有信息发完。 这时候我们有两种选择:
一直在这里等着直到收发数据结束;
每隔一会儿来看看这里有没有数据;
第一种办法虽然可以解决问题,但我们要注意的是对于一个线程进程同时只能处理一个 socket 通信,其他连接只能被阻塞。 显然这种方式在单进程情况下不现实。
第二种办法要比第一种好一些,多个连接可以统一在一定时间内轮流看一遍里面有没有数据要读写,看上去我们可以处理多个连接了,这个方式就是 poll / select 的解决方案。 看起来似乎解决了问题,但实际上,随着连接越来越多,轮询所花费的时间将越来越长,而服务器连接的 socket 大多不是活跃的,所以轮询所花费的大部分时间将是无用的。为了解决这个问题, epoll 被创造出来,它的概念和 poll 类似,不过每次轮询时,他只会把有数据活跃的 socket 挑出来轮询,这样在有大量连接时轮询就节省了大量时间。
具体说说select:select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
select的优点是支持目前几乎所有的平台,缺点主要有如下2个:
1)单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
2)select 所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll则在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
epoll是Linux 2.6 开始出现的为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}
两相对比,可以看出来,epoll只轮询数据活跃的socket,性能自然就比较高了。
而Tornado其实默认是同步阻塞机制的,为了能够实现异步,你就必须使用异步的写法才可以,这里有一个简单的demo:
from tornado.web import RequestHandler
import tornado.ioloop
import tornado.httpclient
import tornado.web
import requests
#异步任务
class AsyncHandler(RequestHandler):
@tornado.web.asynchronous
def get(self):
http_client = tornado.httpclient.AsyncHTTPClient()
http_client.fetch("http://baidu.com",
callback=self.on_fetch)
def on_fetch(self, response):
print(response)
self.write('done')
self.finish()
#同步任务
class SyncHandler(RequestHandler):
def get(self):
response = requests.get("http://baidu.com")
print(response)
self.write('done')
def make_app():
return tornado.web.Application(handlers=[
(r'/async_fetch', AsyncHandler),
(r'/sync_fetch', SyncHandler),
],debug=True)
if __name__ == '__main__':
app = make_app()
app.listen(8000)
tornado.ioloop.IOLoop.current().start()
可以看到异步任务我们使用了(回调)和@tornado.web.asynchronous
@tornado.web.asynchronous 并不能将一个同步方法变成异步,所以修饰在同步方法上是无效的,只是告诉框架,这个方法是异步的,且只能适用于HTTP verb方法(get、post、delete、put等)。@tornado.web.asynchronous 装饰器适用于callback-style的异步方法,对于用@tornado.web.asynchronous 修饰的异步方法,需要主动self.finish()来结束该请求,普通的方法(get()等)会自动结束请求在方法返回的时候。
对比下效率:使用ab命令发送500个请求,每秒50个 ab -n 500 -c 50
结果显而易见,异步效率更高,15秒完成了同步需要50秒的任务。
但是,要想达到异步效果,就必须使用异步写法,让io操作变成异步io,而异步写法对于后台研发的综合素质要求比较高,那么能不能用同步的写法达成异步效果呢?当然可以,就是使用celery+tornado
最后总结一下:
Tornado的异步原理: 单线程的torndo打开一个IO事件循环, 当碰到IO请求(新链接进来 或者 调用api获取数据),由于这些IO请求都是非阻塞的IO,都会把这些非阻塞的IO socket 扔到一个socket管理器,所以,这里单线程的CPU只要发起一个网络IO请求,就不用挂起线程等待IO结果,这个单线程的事件继续循环,接受其他请求或者IO操作,如此循环。
说人话:poll/select: 在一个育婴室内,护士会对育婴室内所有的婴儿挨个check一遍,如此往复。epoll:护士会使用高科技设备对婴儿进行监听,并且只会check生命体征有问题(活跃)的婴儿,如此往复。
另外,对于如果面对超高的并发请求(qps上万),仅仅采用 epoll 模型是不够的,我们还必须使用多进程多线程等方式来充分利用系统资源,这就引出了nginx反向代理tornado进行负载均衡。
原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_107
关于Tornado5.1:到底是真实的异步和还是虚假的异步的更多相关文章
- async/await到底该怎么用?如何理解多线程与异步之间的关系?
前言 如标题所诉,本文主要是解决是什么,怎么用的问题,然后会说明为什么这么用.因为我发现很多萌新都会对之类的问题产生疑惑,包括我最初的我,网络上的博客大多知识零散,刚开始看相关博文的时候,就这样.然后 ...
- C#多线程和异步(三)——一些异步编程模式
一.任务并行库 任务并行库(Task Parallel Library)是BCL中的一个类库,极大地简化了并行编程,Parallel常用的方法有For/ForEach/Invoke三个静态方法.在C# ...
- Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...
- 异步函数封装请确保异步性(Javascript需要养成的良好习惯)
背景假设: 你有许多的配置信息存放在服务器上,因为配置太多,不希望每次都把所有的配置信息都写到前端,希望能需要用的时候再获取就好了. 因为Javascript单线程运行,你不希望堵塞ui渲染于是你专门 ...
- 深入理解JS异步编程五(脚本异步加载)
异步脚本加载 阻塞性脚本 JavaScript在浏览器中被解析和执行时具有阻塞的特性,也就是说,当JavaScript代码执行时,页面的解析.渲染以及其他资源的下载都要停下来等待脚本执行完毕 浏览器是 ...
- Python 协程/异步IO/Select\Poll\Epoll异步IO与事件驱动
1 Gevent 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到 ...
- C# 异步编程1 APM模式异步程序开发
C#已有10多年历史,单从微软2年一版的更新进度来看活力异常旺盛,C#中的异步编程也经历了多个版本的演化,从今天起着手写一个系列博文,记录一下C#中的异步编程的发展历程.广告一下:喜欢我文章的朋友,请 ...
- 同步请求和异步请求的区别,ajax异步请求如何理解
同步请求和异步请求的区别 先解释一下同步和异步的概念 同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式. 异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的 ...
- C# 异步编程3 TPL Task 异步程序开发
.Net在Framework4.0中增加了任务并行库,对开发人员来说利用多核多线程CPU环境变得更加简单,TPL正符合我们本系列的技术需求.因TPL涉及内容较多,且本系列文章为异步程序开发,所以本文并 ...
随机推荐
- MPLS L3 跨域 optionB 配置
mpls跨域optionB optionB的核心思想是私网路由传递过程是 PE-ASBR1-ASBR2-PE2 在传递过程中私网标签发生了变化(由ASBR重新分配了私网标签),而在数据平面(不考虑PH ...
- docker 安装和错误解决方案
安装 x64 架构 install docker switch mirror docker desktop docker run -d -p 80:80 docker/getting-started ...
- Java面试宝典学习笔记【2020】
Java面试题总结 一.Java基础 1)Java有没有goto? goto是C语言中的,通常与条件语句配合使用,可用来实现条件转移, 构成循环,跳出循环体等功能.Java保留了这个关键字但是没有使用 ...
- Java_选择结构
if单选择结构 if(布拉尔表达式){ //如果布拉尔表达式为true将执行的语句 } if双选择结构 if(布拉尔表达式){ //如果布拉尔表达式的值为true }else{ //如果布拉尔表达式的 ...
- 一个全新的Vue拖拽特性实现:“移动”部分
关于拖拽 CabloyJS提供了完备的拖拽特性,可以实现移动和调整尺寸两大类功能,这里对移动的开发进行阐述 关于调整尺寸的开发,请参见:拖拽:调整尺寸 演示 开发步骤 下面以模块test-party为 ...
- numpy中shape的部分解释
转载自:https://blog.csdn.net/qq_28618765/article/details/78081959和https://www.jianshu.com/p/e083512e4f4 ...
- pycharm解释器的配置等
转自:http://www.360doc.com/content/18/0913/14/11881101_786350505.shtml 为什么安装python后,还需要pycharm配置环境 我们实 ...
- c++ 超长整数乘法 高精度乘法
c++ 超长整数乘法 高精度乘法 解题思路 参考加法和减法解题思路 乘法不是一位一位的按照手算的方式进行计算,而是用循环用一个数的某一位去乘另外一个数 打卡代码 #include<bits/st ...
- 这篇SpringCloud GateWay 详解,你用的到
点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 项目源码地址:公众号回复 sentinel,即可免费获取源码 背景 在微服务架构中,通常一个系统 ...
- 关于vue项目中搜索节流的实现
我们经常会遇到这种需求,现在我们在使用百度搜索的时候他们的思想也是根据防抖节流而实现的,至于用防抖还是节流根据自己需求. <template> <input type="t ...