Nodejs 異步式 I/O 與事件式編程

Node.js 最大的特色就是異步式 I/O(或者非阻塞 I/O)與事件緊密結合的編程模式。這 種模式與傳統的同步式 I/O 線性的編程思路有很大的不一樣,由於控制流很大程度上要靠事件 和回調函數來組織,一個邏輯要拆分爲若干個單元html

什麼是阻塞(block)呢?線程在執行中若是遇到磁盤讀寫或網絡通訊(統稱爲 I/O 操做), 一般要耗費較長的時間,這時操做系統會剝奪這個線程的 CPU 控制權,使其暫停執行,同 時將資源讓給其餘的工做線程,這種線程調度方式稱爲 阻塞。當 I/O 操做完畢時,操做系統 將這個線程的阻塞狀態解除,恢復其對CPU的控制權,令其繼續執行。這種 I/O 模式就是通 常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。 4
node

相應地,異步式 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,線程必須有事件循環,不斷地檢查有沒有未處理的事件,依次予 以處理。git

阻塞模式下,一個線程只能處理一項任務,要想提升吞吐量必須經過多線程。而非阻塞 模式下,一個線程永遠在執行計算操做,這個線程所使用的 CPU 核心利用率永遠是 100%, I/O 以事件的方式通知。在阻塞模式下,多線程每每能提升系統吞吐量,由於一個線程阻塞 時還有其餘線程在工做,多線程可讓 CPU 資源不被阻塞中的線程浪費。而在非阻塞模式 下,線程不會被 I/O 阻塞,永遠在利用 CPU。多線程帶來的好處僅僅是在多核 CPU 的狀況 下利用更多的核,而Node.js的單線程也能帶來一樣的好處。這就是爲何 Node.js 使用了單 線程、非阻塞的事件編程模式github

回調函數web

//readfile.jsshell

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

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

} 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"
}
} }
相關文章
相關標籤/搜索