nodejs中的fiber(纖程)庫詳解
fiber/纖程
在操作系統(tǒng)中,除了進程和線程外,還有一種較少應(yīng)用的纖程(fiber,也叫協(xié)程)。纖程常常拿來跟線程做對比,對于操作系統(tǒng)而言,它們都是較輕量級的運行態(tài)。通常認為纖程比線程更為輕量,開銷更小。不同之處在于,纖程是由線程或纖程創(chuàng)建的,纖程調(diào)度完全由用戶代碼控制,對系統(tǒng)內(nèi)核而言,是一種非搶占性的調(diào)度方式,纖程實現(xiàn)了合作式的多任務(wù);而線程和進程則受內(nèi)核調(diào)度,依照優(yōu)先級,實現(xiàn)了搶占式的多任務(wù)。另外,系統(tǒng)內(nèi)核是不知道纖程的具體運行狀態(tài),纖程的使用其實是比較與操作系統(tǒng)無關(guān)。
在node中,單線程是僅針對javascript而言的,其底層其實充斥著多線程。而如果需要在javascript中實現(xiàn)多線程,一種常見的做法是編寫C++ addon,繞過javascript的單線程機制。不過這種方法提升了開發(fā)調(diào)試的難度和成本。像其他很多腳本語言,我們也可以把纖程的概念引入到node中。
node-fibers
node-fibers這個庫就為node提供了纖程的功能。多線程方面沒有測試出理想的結(jié)果,不過在異步轉(zhuǎn)同步作用顯著,也許在減少node調(diào)用堆棧、無限遞歸方面也會有價值可挖。本文檔主要介紹 node-fibers庫的使用方法和異步轉(zhuǎn)同步等內(nèi)容。
安裝
node-fibers是采用C語言編寫,直接下載源碼需要編譯,通常直接npm安裝即可:
npm install fibers
fibers庫的使用
API
1.Fiber(fn)/ new Fiber(fn):
創(chuàng)建一個纖程,可以當成構(gòu)造函數(shù)使用,也可以當成普通函數(shù)調(diào)用。如下例:
function fibo(n) {
return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
Fiber(function () {
console.log(fibo(40));
});
當 run()調(diào)用的時候,纖程啟動,并為 fn分配新的堆棧, fn會在這個新的堆棧上運行,直到 fn有返回值或調(diào)用 yield()。 fn返回后或調(diào)用 yield()后,堆棧重置,當再次調(diào)用 run()時,纖程會再次啟動, fn運行于首次分配的堆棧中。
2.Fiber.current:
獲得當前纖程,并可對其進行操作。如果指定一個變量與其相關(guān)聯(lián),請務(wù)必確保此纖程能夠釋放,否則V8的垃圾回收機制會一直忽略這部分的內(nèi)存,造成內(nèi)存泄漏。
3.Fiber.yield(param):
前面的說明中已經(jīng)提及過這個函數(shù)。 yield()方法用于中斷纖程,一定程度上類似 return。一旦執(zhí)行 yield(),則此 Fiber中后續(xù)代碼將沒有機會執(zhí)行,例如:
var fiber = Fiber(function () {
console.log("Fiber Start");
Fiber.yield();
console.log("Fiber Stop");
}).run();
// 輸出: "Fiber Start"
執(zhí)行后只會輸出“Fiber Start”,后一個輸出命令沒有執(zhí)行。如果向 yield()傳入?yún)?shù),那么此參數(shù)作為 run()的返回值。
var fiber = Fiber(function () {
Fiber.yield("success");
}).run();
console.log(fiber); // -> "success"
4.Fiber.prototype.run(param):
這個方法已經(jīng)很熟悉了,之前隱約有提及調(diào)用 run()的兩種時態(tài),一是Fiber未啟動時,一時Fiber被yield時。在這兩種時態(tài)下, run()的行為并不太一樣。
當Fiber未啟動時, run()接受一個參數(shù),并把它傳遞給 fn,作為其參數(shù)。當Fiber處理yielding狀態(tài)時, run()接受一個參數(shù),并把它作為 yield()的返回值,fn并不會從頭運行,而是從中斷處繼續(xù)運行。關(guān)于 fn、 yield、 run三者的參數(shù)、返回值等關(guān)系,可以通過下面的小例子來說明:
var Fiber = require('fibers');
var fiber = Fiber(function (a) {
console.log("第一次調(diào)用run:");
console.log("fn參數(shù)為:"+a);
var b = Fiber.yield("yield");
console.log("第二次調(diào)用run:");
console.log("fn參數(shù)為:"+a);
console.log("yield返回值為:"+b);
return "return";
});
// 第一次運行run()
var c=fiber.run("One");
// 第二次運行run()
var d=fiber.run("Two");
console.log("調(diào)用yield,run返回:"+c);
console.log("fn運行完成,run返回:"+d);
輸出如下:
/*
第一次調(diào)用run:
fn參數(shù)為:One
第二次調(diào)用run:
fn參數(shù)為:One
yield返回值為:Two
調(diào)用yield,run返回:yield
fn運行完成,run返回:return
*/
從上面例子中,可以很明顯看出 yield的使用方法與現(xiàn)在的javascript的語法相當不同。在別的語言中(C#、Python等)已經(jīng)實現(xiàn)了 yield關(guān)鍵字,作為迭代器的中斷。不妨在node上也實現(xiàn)一個迭代器,具體體會一下 yield的使用。還是以開頭的斐波那契數(shù)列為例:
var fiboGenerator = function () {
var a = 0, b = 0;
while (true) {
if (a == 0) {
a = 1;
Fiber.yield(a);
} else {
b += a;
b == a ? a = 1 : a = b - a;
Fiber.yield(b);
}
}
}
var f = new Fiber(fiboGenerator);
f.next = f.run;
for (var i = 0; i < 10; i++) {
console.log(f.next());
}
輸出為:
/*
1
1
2
3
5
8
13
21
34
55
*/
有兩個問題需要留意,第一, yield說是方法,更多地像關(guān)鍵字,與 run不同, yield不需要依托Fiber實例,而 run則需要。如果在Fiber內(nèi)部調(diào)用 run,則一定要使用: Fiber.current.run();第二, yield本身為javascript的保留關(guān)鍵字,不確定是否會、何時會啟用,所以代碼在將來可能會面臨變更。
5.Fiber.prototype.reset():
我們已經(jīng)知道Fiber可能存在不同的時態(tài),同時會影響 run的行為。而 reset方法則不管Fiber處理什么狀態(tài),都恢復(fù)到初始狀態(tài)。隨后再執(zhí)行 run,就會重新運行 fn。
6.Fiber.prototype.throwInto(Exception):
本質(zhì)上 throwInto會拋出傳給它的異常,并將異常信息作為 run的返回值。如果在Fiber內(nèi)不對它拋出的異常作處理,異常會繼續(xù)冒泡。不管異常是否處理,它會強制 yield,中斷Fiber。
future庫的使用
在node中直接使用Fiber并不一直是合理的,因為Fiber的API實在簡單,實際使用中難免會產(chǎn)生重復(fù)冗長的代碼,不利于維護。推薦在node與Fiber之間增加一層抽象,讓Fiber能夠更好地工作。 future庫就提供了這樣一種抽象。 future庫或者任何一層抽象也許都不是完美的,沒有誰對誰錯,只有適用不適用。比如, future庫向我們提供了簡單的API能夠完成異步轉(zhuǎn)同步的工作,然而它對封裝 generator (類似上面的斐波那契數(shù)列生成器)則無能為力。
future庫不需要單獨下載安裝,已經(jīng)包含在 fibers庫中,使用時只需要 var future=require('fibers/future') 即可。
API
1.Function.prototype.future():
給 Function類型添加了 future方法,將function轉(zhuǎn)化成一個“funture-function”。
var futureFun = function power(a) {
return a * a;
}.future();
console.log(futureFun(10).wait());
實際上 power方法是在Fibel內(nèi)執(zhí)行的。不過現(xiàn)有版本的 future有bug,官方?jīng)]有具體的說明,如果需要使用此功能,請刪除掉 future.js的第339行和第350行。
2.new Future()
Future對象的構(gòu)造函數(shù),下文詳細介紹。
3.Future.wrap(fn, idx)
wrap方法封裝了異步轉(zhuǎn)同步的操作,是 future庫中對我們最有價值的方法。 fn表示需要轉(zhuǎn)換的函數(shù), idx表示 fn接受的參數(shù)數(shù)目,認為其 callback方法為最后一個參數(shù)(這邊API的制定頗有爭議,有人傾向傳遞 callback應(yīng)該處于的位置,好在 wrap方法比較簡單,可以比較容易修改代碼)??匆粋€例子就能了解 wrap的用法:
var readFileSync = Future.wrap(require("fs").readFile);
Fiber(function () {
var html = readFileSync("./1.txt").wait().toString();
console.log(html);
}).run();
從這個例子中可以看出Fiber異步轉(zhuǎn)同步確實非常有效,除了語法上多了一步 .wait()外,其他已經(jīng) fs提供的 fs.readFileSync方法別無二致了。
4.Future.wait(futures):
這個方法前面已經(jīng)多次看到了。顧名思義,它的作用就是等待結(jié)果。如果要等待一個future的實例的結(jié)果,直接調(diào)用 futureInstance.wait()即可;如果需要等待一系列future實例的結(jié)果,則調(diào)用 Future.wait(futuresArray)。需要注意的是,在第二種用法中,一個future實例在運行時出現(xiàn)錯誤, wait方法不會拋出錯誤,不過我們可以使用 get()方法直接獲取運行結(jié)果。
5.Future.prototype.get():
get()的用法與 wait()的第一種方式很像,所不同的是, get()立刻返回結(jié)果。如果數(shù)據(jù)沒有準備好, get()會拋出錯誤。
6.Future.prototype.resolve(param1,param2):
上面的的 wrap方法總給人以一種 future其實在吞噬異步方法的回調(diào)函數(shù),并直接返回異步結(jié)果。事實上 future也通過 resolve方法提供設(shè)置回調(diào)函數(shù)的解決方案。 resolve最多接受兩個參數(shù),如果只傳入一個參數(shù), future認為傳了一個node風(fēng)格的回調(diào)函數(shù),例如如下示例:
futureInstance.resolve(function (err, data) {
if (err) {
throw err;
} else {
console.log(data.toString());
}
});
如果傳入兩個參數(shù),則表示對錯誤和數(shù)據(jù)分別做處理,示例如下:
futureInstance.resolve(function (err) {
throw err;
}, function (data) {
console.log(data.toString());
});
另外 future并不區(qū)分 resolve的調(diào)用時機,如果數(shù)據(jù)沒有準備好,則將回調(diào)函數(shù)壓入隊列,由 resolver()方法統(tǒng)一調(diào)度,否則直接取數(shù)據(jù)立即執(zhí)行回調(diào)函數(shù)。
7.Future.prototype.isResolved():
返回布爾值,表示操作是否已經(jīng)執(zhí)行。
8.Future.prototype.proxy(futureInstance):
proxy方法提供一種 future實例的代理,本質(zhì)上是對 resolve方法的包裝,其實是將一個instance的回調(diào)方法作為另一個instance的回調(diào)執(zhí)行者。例如:
var target = new Future;
target.resolve(function (err, data) {
console.log(data)
});
var proxyFun = function (num, cb) {
cb(null, num * num);
};
Fiber(function () {
var proxy = Future.wrap(proxyFun)(10);
proxy.proxy(target);
}).run(); // 輸出100
雖然執(zhí)行的是 proxy,但是最終 target的回調(diào)函數(shù)執(zhí)行了,并且是以 proxy的執(zhí)行結(jié)果驅(qū)動 target的回調(diào)函數(shù)。這種代理手段也許在我們的實際應(yīng)用中有很大作用,我暫時還沒有深入地思考過。
9.Future.prototype.return(value):
10.Future.prototype.throw(error):
11.Future.prototype.resolver():
12.Future.prototype.detach():
以上四個API呢我感覺相對于別的API,實際使用的場景或作用比較一般。 return和 throw都受 resolver方法調(diào)度,這三個方法都很重要,在正常的future使用流程中都會默默工作著,只是我沒有想出具體單獨使用它們的場景,所以沒有辦法具體介紹。 detach方法只能算 resolve方法的簡化版,亦沒有介紹的必要。
相關(guān)文章
利用node實現(xiàn)一個批量重命名文件的函數(shù)
這篇文章主要給大家介紹了關(guān)于利用node實現(xiàn)一個批量重命名文件的函數(shù)的相關(guān)資料,文中通過示例示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12Node.js發(fā)出請求走Proxyman代理調(diào)試tip詳解
這篇文章主要為大家介紹了Node.js發(fā)出請求走Proxyman代理調(diào)試tip詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08Nodejs 和Session 原理及實戰(zhàn)技巧小結(jié)
這篇文章主要介紹了Nodejs 和Session 原理及實戰(zhàn)技巧小結(jié),需要的朋友可以參考下2017-08-08nodejs如何獲取當前連接的網(wǎng)絡(luò)ip
這篇文章主要介紹了nodejs如何獲取當前連接的網(wǎng)絡(luò)ip問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10Nodejs 中的 Buffer 類的創(chuàng)建與基本使用
這篇文章主要為大家介紹了Nodejs中Buffer的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10