深入浅析Node.js单线程模型


Posted in Javascript onJuly 10, 2017

Node.js采用 事件驱动 和 异步I/O 的方式,实现了一个单线程、高并发的运行时环境,而单线程就意味着同一时间只能做一件事,那么Node.js如何利用单线程来实现高并发和异步I/O?本文将围绕这个问题来探讨Node.js的单线程模型:

1、高并发

一般来说,高并发的解决方案就是多线程模型,服务器为每个客户端请求分配一个线程,使用同步I/O,系统通过线程切换来弥补同步I/O调用的时间开销,比如Apache就是这种策略,由于I/O一般都是耗时操作,因此这种策略很难实现高性能,但非常简单,可以实现复杂的交互逻辑。

而事实上,大多数网站的服务器端都不会做太多的计算,它们只是接收请求,交给其它服务(比如从数据库读取数据),然后等着结果返回再发给客户端。因此,Node.js针对这一事实采用了单线程模型来处理,它不会为每个接入请求分配一个线程,而是用一个主线程处理所有的请求,然后对I/O操作进行异步处理,避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。

2、事件循环

Node.js 在主线程中维护了一个事件队列,当接收到请求后,就将请求作为一个事件放入该队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非I/O任务,就亲自处理,并通过回调函数返回到上层调用;如果是I/O任务,就从线程池中拿出一个线程来执行这个事件,并指定回调函数,然后继续循环队列中的其他事件。当线程中的I/O任务完成后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫事件循环(Event Loop),如下图所示:

深入浅析Node.js单线程模型

这个图是整个Node.js的运行原理,从左到右,从上到下,Node.js被分成了四层,分别是应用层、V8引擎层、Node API层 和 LIBUV层,

应用层:   即Javascript交互层,常见的就是Node.js的模块,比如 http,fs
V8引擎层:  即利用V8引擎来解析Javascript语法,进而和下层API交互
NodeAPI层:  为上层模块提供系统调用,一般是由C语言来实现,和操作系统进行交互
LIBUV层: 即Event Loop,是Node.js实现异步的核心,由LIBUV库来实现,而LIBUV中的线程池是由操作系统内核接受管理的。

从上述理解来看,Node.js的单线程仅仅是指Javascript运行在单线程中,而并非Node.js是单线程,在Node中,无论是Linux平台还是Windows平台,内部都是通过线程池来完成IO操作,而LIBUV就是针对不同平台的差异性实现了统一调用。

3、事件驱动

总结上面的过程可以发现,Node.js的核心是使用事件驱动模式实现了异步I/O,为了更具体、更清晰的理解和接受这个事实,我们用代码来描述Node.js的事件驱动模型:

3.1、事件队列

首先,我们需要定义一个事件队列,既然是队列,那就是一个先进先出(FIFO)的数据结构,我们用JS的数组来描述,如下:

/**
 * 定义事件队列
 * 入队:unshfit()
 * 出队:pop()
 * 空队列:length == 0
 */
eventQueue:[],

为了方便理解,我们规定:数组的第一个元素是队列的尾部,数组的最后一个元素是队列的头部, unshfit 就是在尾部插入一个元素,pop就是从头部弹出一个元素,这样就实现了一个简单的队列。

3.2、接收请求

定义一个总的入口来接收用户请求,如下所示:

/**
 * 接收用户请求
 * 每一个请求都会进入到该函数
 * 传递参数request和response
 */
processHttpRequest:function(request,response){
   
  //定义一个事件对象
  var event = createEvent({
    params:request.params, //传递请求参数
    result:null, //存放请求结果
    callback:function(){} //指定回调函数
  });
 
  //在队列的尾部添加该事件 
  eventQueue.unshift(event);
},

这个函数很简单,就是把用户的请求包装成事件,放到队列里,然后继续接收其他请求。

3.3、事件循环

当主线程处于空闲时就开始循环事件队列,所以,我们再定义一个事件循环的函数:

/**
 * 事件循环主体,主线程择机执行
 * 循环遍历事件队列
 * 处理事件
 * 执行回调,返回给上层
 */
eventLoop:function(){
  //如果队列不为空,就继续循环
  while(this.eventQueue.length > 0){
    //从队列的头部拿出一个事件
    var event = this.eventQueue.pop();
    //如果是IO任务
    if(isIOTask(event)){
      //从线程池里拿出一个线程
      var thread = getThreadFromThreadPool();
      //交给线程处理
      thread.handleIOTask(event)
    }else {
      //非IO任务处理后,直接返回结果
      var result = handleEvent(event);
      //最终通过回调函数返回给V8,再由V8返回给应用程序
      event.callback.call(null,result);
    }
  }
},

主线程不停的检测事件队列,对于IO任务就交给线程池来处理,非IO任务就自己处理并返回。

3.4、线程池

线程池接到任务以后,直接处理IO操作,比如读取数据库:

当IO

/**
 * 处理IO任务
 * 完成后将事件添加到队列尾部
 * 释放线程
 */
handleIOTask:function(event){
  //当前线程
  var curThread = this;
 
  //操作数据库
  var optDatabase = function(params,callback){
    var result = readDataFromDb(params);
    callback.call(null,result)
  };
   
  //执行IO任务
  optDatabase(event.params,function(result){
    //返回结果存入事件对象中
    event.result = result;
 
    //IO完成后,将不再是耗时任务
    event.isIOTask = false;
     
    //将该事件重新添加到队列的尾部
    this.eventQueue.unshift(event);
     
    //释放当前线程
    releaseThread(curThread)
  })
}

任务完成以后就执行回调,把请求结果存入事件中,并将该事件重新放入队列中,等待循环,最后释放线程。当主线程再次循环到该事件时,就直接处理了。

 4、Node.js软肋

以上四步简单描述了Node.js事件驱动模型,至此,我们对Node.js应该有了一个简单而又清晰的认识,但Node.js 并不是什么都能做。

上面提到,如果是I/O任务,Nodejs就把任务交给线程池来异步处理,高效简单,因此Node.js适合处理I/O密集型任务,但不是所有的任务都是I/O密集型任务,当碰到CPU密集型任务时,就是只用CPU计算的操作,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),这时Node.js就会亲自处理,一个一个的计算,前面的任务没有执行完,后面的任务只能干等着,如下图所示:

深入浅析Node.js单线程模型

在事件队列中,如果前面的CPU计算任务没有完成,那么后面的任务就会被阻塞,出现响应缓慢的情况,如果操作系统本身就是单核,那也就算了,但现在大部分服务器都是多CPU或多核的,而Node.js只有一个EventLoop,也只占用一个CPU/内核,当Node.js被CPU密集型任务占用,导致其他任务被阻塞时,却还有CPU/内核处理闲置状态,造成资源浪费。因此Node.js不适合CPU密集型任务。

 

5、Node.js适用场景

5.1、RESTful API

这是适合 Node 的理想情况,因为您可以构建它来处理数万条连接。它仍然不需要大量逻辑;它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的 API 需求。

5.2、实时程序

比如聊天服务,聊天应用程序是最能体现 Node.js 优点的例子:轻量级、高流量并且能良好的应对跨平台设备上运行密集型数据(虽然计算能力低)。同时,聊天也是一个非常值得学习的用例,因为它很简单,并且涵盖了目前为止一个典型的 Node.js 会用到的大部分解决方案。

以上所述是小编给大家介绍的Node.js单线程模型,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
EasySlider 基于jQuery功能强大简单易用的滑动门插件
Jun 11 Javascript
div失去焦点事件实现思路
Apr 22 Javascript
javascript常用功能汇总
Jul 05 Javascript
javascript封装简单实现方法
Aug 11 Javascript
一道JS前端闭包面试题解析
Dec 25 Javascript
学习JavaScript设计模式之状态模式
Jan 08 Javascript
Bootstrap select实现下拉框多选效果
Dec 23 Javascript
html5 canvas 详细使用教程
Jan 20 Javascript
利用JS hash制作单页Web应用的方法详解
Oct 10 Javascript
JS关于刷新页面的相关总结
May 09 Javascript
layui button 按钮弹出提示窗口,确定才进行的方法
Sep 06 Javascript
微信小程序关键字变色实现代码实例
Dec 13 Javascript
require.js中的define函数详解
Jul 10 #Javascript
vue.js组件之间传递数据的方法
Jul 10 #Javascript
Node.js+Express+MySql实现用户登录注册功能
Jul 10 #Javascript
基于jQuery Easyui实现登陆框界面
Jul 10 #jQuery
Angular X中使用ngrx的方法详解(附源码)
Jul 10 #Javascript
angular实现spa单页面应用实例
Jul 10 #Javascript
JavaScript 程序错误Cannot use 'in' operator to search的解决方法
Jul 10 #Javascript
You might like
在数据量大(超过10万)的情况下
2007/01/15 PHP
利用PHP实现短域名互转
2013/07/05 PHP
PHP批量去除BOM头内容信息代码
2016/03/11 PHP
总结对比php中的多种序列化
2016/08/28 PHP
php使用QueryList轻松采集js动态渲染页面方法
2018/09/11 PHP
JavaScript 捕获窗口关闭事件
2009/07/26 Javascript
将文本输入框内容加入表中的js代码
2013/08/18 Javascript
JavaScript事件委托用法分析
2015/01/24 Javascript
jQuery解决浏览器兼容性问题案例分析
2016/04/15 Javascript
无需 Flash 使用 jQuery 复制文字到剪贴板
2016/04/26 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
2016/07/21 Javascript
AngularJS入门教程之表格实例详解
2016/07/27 Javascript
微信小程序 五星评分(包括半颗星评分)实例代码
2016/12/14 Javascript
超级简易的JS计算器实例讲解(实现加减乘除)
2017/08/08 Javascript
Node.js实现注册邮箱激活功能的方法示例
2018/03/23 Javascript
JS利用prototype给类添加方法操作详解
2019/06/21 Javascript
JavaScript编写开发动态时钟
2020/07/29 Javascript
我所理解的JavaScript中的this指向
2020/09/04 Javascript
vue实现按钮切换图片
2021/01/20 Vue.js
Python openpyxl 遍历所有sheet 查找特定字符串的方法
2018/12/10 Python
关于python多重赋值的小问题
2019/04/17 Python
python读取hdfs上的parquet文件方式
2020/06/06 Python
Python fileinput模块如何逐行读取多个文件
2020/10/05 Python
全球领先的全景影像品牌:Insta360
2019/08/21 全球购物
英国珠宝和手表专家:Pleasance & Harper
2020/10/21 全球购物
Java基础面试题
2014/07/19 面试题
中学教师实习自我鉴定
2013/09/28 职场文书
岗位竞聘演讲稿范文
2014/04/24 职场文书
高中英语演讲稿范文
2014/04/24 职场文书
2014年三万活动总结
2014/04/26 职场文书
汽车广告策划方案
2014/05/31 职场文书
知识竞赛拉拉队口号
2014/06/16 职场文书
个人四风问题原因分析及整改措施
2014/09/28 职场文书
医德医风个人工作总结2014
2014/11/14 职场文书
销售督导岗位职责
2015/04/10 职场文书
干部培训工作总结2015
2015/05/25 职场文书