zoukankan      html  css  js  c++  java
  • Nodejs 异步式 I/O 与事件式编程

    Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。这 种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件 和回调函数来组织,一个逻辑要拆分为若干个单元

    什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作), 通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同 时将资源让给其他的工作线程,这种线程调度方式称为 阻塞。当 I/O 操作完毕时,操作系统 将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种 I/O 模式就是通 常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。 4

    相应地,异步式 I/O (Asynchronous I/O)或非阻塞式 I/O (Non-blocking I/O)则针对 所有 I/O 操作不采用阻塞的策略。当线程遇到 I/O 操作时,不会以阻塞的方式等待 I/O 操作 的完成或数据的返回,而只是将 I/O 请求发送给操作系统,继续执行下一条语句。当操作 系统完成 I/O 操作时,以事件的形式通知执行 I/O 操作的线程,线程会在特定时候处理这个 事件。为了处理异步 I/O,线程必须有事件循环,不断地检查有没有未处理的事件,依次予 以处理。

    阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞 模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU 核心利用率永远是 100%, I/O 以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞 时还有其他线程在工作,多线程可以让 CPU 资源不被阻塞中的线程浪费。而在非阻塞模式 下,线程不会被 I/O 阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU 的情况 下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js 使用了单 线程、非阻塞的事件编程模式

    回调函数

    //readfile.js

    var fs = require('fs');
    fs.readFile('file.txt', 'utf-8',
    function(err, data) {

    if (err) { console.error(err);

    } else { console.log(data);

    } });

        console.log('end.');
    
    

    运行的结果如下:

        end.
    Contents of the file.
    
    
    看到没有各位 先输出end  后才输出内容,为什么这样啊?因为是异步非阻塞的。function就是一个回调函数 。Node.js 中,异步式 I/O 是通过回调函数来实现的
    

    fs.readFile 调用时所做的工作只是将异步式 I/O 请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end.,再看到file.txt 文件的内容

    Node.js 中,并不是所有的 API 都提供了同步和异步版本。Node.js 不鼓励使用同步IO

    Node.js 的事件循环机制

    Node.js 在什么时候会进入事件循环呢?答案是 Node.js 程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束

    模块和包

    什么是模块?

    模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展

    创建及加载模块

    在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问 题仅仅在于如何在其他文件中获取这个模块

    创建模块 module.js

    var name;

    exports.setName = function(mName){

    name = mName;

    };

    exports.sayHello = function() {

    console.log('Hello,' + name);

    };

    创建runModule.js里面代码如下:

    var module = require('./module.js');

    module.setName('greenboy');

    module.sayHello();

    在shell里面运行node runModule.js 产生结果如下:
    201506022316.jpg


    朋友 想想 若是我在一个js文件里面多次调用 require(‘./module’); 并将其分别赋值给变量,第二次执行完毕后,结果是什么呢?
    如下创建loadModule.js文件代码:

    var test01 = require('./module');
    test01.setName('test01');

    var test02 = require('./module');
    test02.setName('test02');

    test01.sayHello();


    201506032148.jpg
    有点类似于创建一个对象,但实际上和对象又有本质的区别,因为 require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个,后面创建的肯定将前面的给覆盖掉。


    创建singleobject.js文件

    function Hello() {
    var name;
    this.setName = function(nName){
    name = nName;
    };
    this.sayHello = function() {
    console.log('Hello ' + name);
    };
    };

    exports.Hello = Hello;


    引入的时候就需要使用 require('./singleObject').Hello

    可以使用下面的方法简化 创建hello.js

    function Hello() {
    var name;
    this.setName = function(nName){
    name = nName;
    };
    this.sayHello = function() {
    console.log('Hello ' + name);
    };
    };
    module.exports = Hello;


    创建getHelllo.js 如下:

    //var Hello = require('./hello');
    var Hello = require('./singleObject').Hello;
    var Hello2 = require('./singleObject').Hello;
    hello = new Hello();
    hello.setName('hello1');
    hello2 = new Hello2();
    hello2.setName('hello2');
    hello.sayHello();
    hello2.sayHello();

    可以取消注释进行来回验证:
    201506032212.jpg


    这个时候的值一直都类似对象,想想为什么?因为js里面的闭包机制,每次访问都是一个闭包,访问函数内的变量。



    事实上,exports 本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本 质上是通过它为模块闭包1的内部建立了一个有限的访问接口。不可以通过对 exports 直接赋值代替对 module.exports 赋值。 exports 实际上只是一个和 module.exports 指向同一个对象的变量, 它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定 module.exports 来改变访问接口。


    创建包

    Node.js 对包的要求 : 只要顶层目录下有 package.json,并符合一些规范 即可


    package.json 是 CommonJS 规定的用来描述包的文件,完全符合规范的 package.json 文 件应该含有以下字段。
     name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含 空格。
     description:包的简要说明。
     version:符合语义化版本识别1规范的版本字符串。
     keywords:关键字数组,通常用于搜索。
     maintainers:维护者数组,每个元素要包含 name、email (可选)、web (可选)
    字段。
     contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者
    数组的第一个元素。
     bugs:提交bug的地址,可以是网址或者电子邮件地址。
     licenses:许可证数组,每个元素要包含 type (许可证的名称)和 url (连接到许可证文本的地址)字段
     repositories:仓库托管地址数组,每个元素要包含 type(仓库的类型,如 git )、url (仓库的地址)和 path (相对于仓库的路径,可选)字段
     dependencies:包的依赖,一个关联数组,由包名称和版本号组成

    下面是个完整的例子:
    {
    "name": "mypackage",
    "description": "Sample package for CommonJS. This package demonstrates the required
    elements of a CommonJS package.",
    "version": "0.7.0",
    "keywords": [
    "package",
    "example" ],
    "maintainers": [
    {
    "name": "Bill Smith",
    "email": "bills@example.com",
    }
    ],
    "contributors": [
    {
    "name": "BYVoid",
    "web": "http://www.byvoid.com/"
    } ],
    "bugs": {
    "mail": "dev@example.com",
    "web": "http://www.example.com/bugs"
    },
    "licenses": [
    {
    "type": "GPLv2",
    "url": "http://www.example.org/licenses/gpl.html"
    } ],
    "repositories": [
    {
    "type": "git",
    "url": "http://github.com/BYVoid/mypackage.git"
    }
    ],
    "dependencies": {
    "webkit": "1.2",
    "ssl": {
    "gnutls": ["1.0", "2.0"],
    "openssl": "0.9.8"
    }
    } }





















  • 相关阅读:
    Redis基本概念、基本使用与单机集群部署
    Storm安装部署
    HBase单机和集群版部署
    Hive基础概念、安装部署与基本使用
    Hadoop — HDFS的概念、原理及基本操作
    随机森林
    深度学习入门: CNN与LSTM(RNN)
    word修改页眉使本页的页眉与别的页不一样
    几个值得学习的Java博客
    【转】求最短路径长度--简单易懂
  • 原文地址:https://www.cnblogs.com/greenboy/p/4545297.html
Copyright © 2011-2022 走看看