热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NodeJS的异步I/O

这篇博客基于对《深入浅出Node.js》第三章的理解(因为理解的不全面所以只描述看懂的部分)。我认为模块机制和异步IO是node.js中最重要的部分,尽管可能在很多时候并不会被直接使用。通

     这篇博客基于对《深入浅出Node.js》第三章的理解(因为理解的不全面所以只描述看懂的部分)。我认为模块机制和异步I/O是node.js中最重要的部分,尽管可能在很多时候并不会被直接使用。通过这两个部分了解node.js以后,再使用一些应用型功能可能会理解的更多。

     浏览器中Javascript执行与UI渲染共用一个线程,所以在浏览器中网络请求多采用异步的方式。原因是我们希望实现并行I/O。并行I/O的意义在于当当前线程需要进行I/O请求时,操作系统可以调度其他线程进行CPU计算,以充分利用资源。

     异步/同步与阻塞/非阻塞是两回事。操作系统内核对于I/O只有两种方式:阻塞与非阻塞。阻塞I/O的调用的结束是以操作系统内核完成所有操作(以读取磁盘文件为例,包括寻道、读取数据、复制数据到内存)为标志,而应用程序也需要等到阻塞I/O调用结束后才返回结果。所以阻塞I/O可能会造成CPU对I/O不必要的等待。对于非阻塞I/O,应用程序在向系统内核发送I/O请求(比如文件读取、网络请求)得到文件描述符后,可以继续执行其他操作。但是在之后的时间里,需要根据文件描述符向系统内核发送请求确认读取数据是否完成,或者由内核通知应用程序数据读取的结束。目前正在使用的一种比较好的方式是当数据读取结束后,由系统内核通知应用程序数据的完成,然后应用程序执行回调。

     这种机制实现的方式是通过多线程。让部分线程通过阻塞I/O或者非阻塞I/O的方式加轮询技术(反复询问内核数据是否读取完毕)来完成数据获取,其他线程进行计算操作,来模拟应用程序与内核之间的通信(实际上是应用程序的I/O线程与内核通信,应用程序的计算线程与I/O线程通信)。如图1所示(图中主线程,我的理解就是应用程序的计算线程)。

     

                                                图1 异步I/O

    *nix与windows操作系统内核都有相应的机制来支持上述异步I/O模型(*nix是自定义线程池,windows是IOCP),node.js通过libuv库屏蔽操作系统差异。所以node.js并不是单线程的,只是Javascript执行在单线程中,I/O线程另有线程池。并且用户线程是单线程的,但是I/O线程之间是可以并行的,比如文件I/O与磁盘I/O。在windows中线程池由操作系统提供(iocp),在*nix中由node的libuv模块实现。

   至于node.js对于异步I/O具体的实现,分为事件循环、观察者和请求对象三个环节。

   事件循环:

   事件循环是node.js自身的执行模型。node进程启动时,便会创建一个类似while(true)的循环,每执行一次循环体的过程我们称为一个tick。每个tick中,查看是否有事件待处理。如果有,就取出回调执行。(书中说没有事件了进程就结束了,难道没有事件了node进程就退出了嘛。。。什么叫做没有事件了?)

   观察者:

   在每个tick中,如何判断是否有事件需要处理呢?这里必须要引入的概念是观察者。每个事件循环中有1个或多个观察者,判断是否有事件处理就是通过向观察者询问是否有要处理的事件。观察者分为文件I/O观察者,网络I/O观察者等。我的理解是观察者是node的一部分。

   请求对象:

   在我们通过Javascript发起异步请求到内核执行完I/O操作之间,有一个中间产物叫做请求对象。请求参数与请求回调都被写在请求对象中。这个对象由libuv创建(或者操作系统),被推入线程池中等待执行,至此Javascript的异步调用已经返回了。当线程池中I/O操作调用完毕以后,会将操作结果写入请求对象中req对象的result属性,并通知操作系统的异步模块(比如在windows中是IOCP)。在每次tick的过程中,观察者会调用操作系统提供的接口检查是否有执行完的操作,如果有,将请求对象加入观察者的执行队列中。这些请求对象被当作事件来处理,I/O观察者将请求对象中的回调取出并执行。至此异步I/O的流程结束。完整流程如图2所示(回调函数是观察者执行的吗?)

                                                 图2 异步I/O流程

   非异步I/O的异步API

   主要谈定时器。定时器的实现也是通过定时器观察者,只是与I/O线程池无关。调用setTimeout或者setInterval时,一个定时器对象被创建,并且被加入到定时器观察者内部的红黑树中,每次tick会取出该对象,查看是否到时。如果超时,则行成事件,回调函数将立即执行。定时器并不总是准确的,如果定时器被设定在10ms之后执行,但是9ms之后有一个任务占用了5ms,则轮到定时器回调执行时就已经过期了4ms(也就是很有可能上次tick时轮到该该定时器时时间未到,本次tick轮到它时已经过期了)。setTimeout 0的机制正在于此。但是setTimeout0不如process.nextTick()精准。

  综上,node通过事件驱动的方式处理请求,而不是为每一个请求创建一个线程。nginx也是事件驱动的工作方式。

  

   

  

   

   

 

   

    

   


推荐阅读
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了Oracle存储过程的基本语法和写法示例,同时还介绍了已命名的系统异常的产生原因。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
author-avatar
Cri_Hello
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有