nodejs中的fiber(纤程)库详解


Posted in NodeJs onMarch 24, 2015

fiber/纤程

在操作系统中,除了进程和线程外,还有一种较少应用的纤程(fiber,也叫协程)。纤程常常拿来跟线程做对比,对于操作系统而言,它们都是较轻量级的运行态。通常认为纤程比线程更为轻量,开销更小。不同之处在于,纤程是由线程或纤程创建的,纤程调度完全由用户代码控制,对系统内核而言,是一种非抢占性的调度方式,纤程实现了合作式的多任务;而线程和进程则受内核调度,依照优先级,实现了抢占式的多任务。另外,系统内核是不知道纤程的具体运行状态,纤程的使用其实是比较与操作系统无关。

在node中,单线程是仅针对javascript而言的,其底层其实充斥着多线程。而如果需要在javascript中实现多线程,一种常见的做法是编写C++ addon,绕过javascript的单线程机制。不过这种方法提升了开发调试的难度和成本。像其他很多脚本语言,我们也可以把纤程的概念引入到node中。

node-fibers

node-fibers这个库就为node提供了纤程的功能。多线程方面没有测试出理想的结果,不过在异步转同步作用显著,也许在减少node调用堆栈、无限递归方面也会有价值可挖。本文档主要介绍 node-fibers库的使用方法和异步转同步等内容。

安装

node-fibers是采用C语言编写,直接下载源码需要编译,通常直接npm安装即可:

npm install fibers

fibers库的使用

API

1.Fiber(fn)/ new Fiber(fn):

创建一个纤程,可以当成构造函数使用,也可以当成普通函数调用。如下例:

function fibo(n) {

    return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;

}

Fiber(function () {

    console.log(fibo(40));

});

当 run()调用的时候,纤程启动,并为 fn分配新的堆栈, fn会在这个新的堆栈上运行,直到 fn有返回值或调用 yield()。 fn返回后或调用 yield()后,堆栈重置,当再次调用 run()时,纤程会再次启动, fn运行于首次分配的堆栈中。

2.Fiber.current:

获得当前纤程,并可对其进行操作。如果指定一个变量与其相关联,请务必确保此纤程能够释放,否则V8的垃圾回收机制会一直忽略这部分的内存,造成内存泄漏。

3.Fiber.yield(param):

前面的说明中已经提及过这个函数。 yield()方法用于中断纤程,一定程度上类似 return。一旦执行 yield(),则此 Fiber中后续代码将没有机会执行,例如:

var fiber = Fiber(function () {

    console.log("Fiber Start");

    Fiber.yield();

    console.log("Fiber Stop");

}).run();

// 输出: "Fiber Start"

执行后只会输出“Fiber Start”,后一个输出命令没有执行。如果向 yield()传入参数,那么此参数作为 run()的返回值。

var fiber = Fiber(function () {

    Fiber.yield("success");

}).run();

console.log(fiber); // -> "success"

4.Fiber.prototype.run(param):

这个方法已经很熟悉了,之前隐约有提及调用 run()的两种时态,一是Fiber未启动时,一时Fiber被yield时。在这两种时态下, run()的行为并不太一样。
当Fiber未启动时, run()接受一个参数,并把它传递给 fn,作为其参数。当Fiber处理yielding状态时, run()接受一个参数,并把它作为 yield()的返回值,fn并不会从头运行,而是从中断处继续运行。关于 fn、 yield、 run三者的参数、返回值等关系,可以通过下面的小例子来说明:

var Fiber = require('fibers');

var fiber = Fiber(function (a) {

    console.log("第一次调用run:");

    console.log("fn参数为:"+a);

    var b = Fiber.yield("yield");

    console.log("第二次调用run:");

    console.log("fn参数为:"+a);

    console.log("yield返回值为:"+b);

    return "return";

});

// 第一次运行run()

var c=fiber.run("One");

// 第二次运行run()

var d=fiber.run("Two");

console.log("调用yield,run返回:"+c);

console.log("fn运行完成,run返回:"+d);

输出如下:

/*

第一次调用run:

fn参数为:One

第二次调用run:

fn参数为:One

yield返回值为:Two

调用yield,run返回:yield

fn运行完成,run返回:return

*/

从上面例子中,可以很明显看出 yield的使用方法与现在的javascript的语法相当不同。在别的语言中(C#、Python等)已经实现了 yield关键字,作为迭代器的中断。不妨在node上也实现一个迭代器,具体体会一下 yield的使用。还是以开头的斐波那契数列为例:

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说是方法,更多地像关键字,与 run不同, yield不需要依托Fiber实例,而 run则需要。如果在Fiber内部调用 run,则一定要使用: Fiber.current.run();第二, yield本身为javascript的保留关键字,不确定是否会、何时会启用,所以代码在将来可能会面临变更。

5.Fiber.prototype.reset():

我们已经知道Fiber可能存在不同的时态,同时会影响 run的行为。而 reset方法则不管Fiber处理什么状态,都恢复到初始状态。随后再执行 run,就会重新运行 fn。

6.Fiber.prototype.throwInto(Exception):

本质上 throwInto会抛出传给它的异常,并将异常信息作为 run的返回值。如果在Fiber内不对它抛出的异常作处理,异常会继续冒泡。不管异常是否处理,它会强制 yield,中断Fiber。

future库的使用

在node中直接使用Fiber并不一直是合理的,因为Fiber的API实在简单,实际使用中难免会产生重复冗长的代码,不利于维护。推荐在node与Fiber之间增加一层抽象,让Fiber能够更好地工作。 future库就提供了这样一种抽象。 future库或者任何一层抽象也许都不是完美的,没有谁对谁错,只有适用不适用。比如, future库向我们提供了简单的API能够完成异步转同步的工作,然而它对封装 generator (类似上面的斐波那契数列生成器)则无能为力。

future库不需要单独下载安装,已经包含在 fibers库中,使用时只需要 var future=require('fibers/future') 即可。

API

1.Function.prototype.future():

给 Function类型添加了 future方法,将function转化成一个“funture-function”。

var futureFun = function power(a) {

    return a * a;

}.future();

console.log(futureFun(10).wait());

实际上 power方法是在Fibel内执行的。不过现有版本的 future有bug,官方没有具体的说明,如果需要使用此功能,请删除掉 future.js的第339行和第350行。

2.new Future()

Future对象的构造函数,下文详细介绍。

3.Future.wrap(fn, idx)

wrap方法封装了异步转同步的操作,是 future库中对我们最有价值的方法。 fn表示需要转换的函数, idx表示 fn接受的参数数目,认为其 callback方法为最后一个参数(这边API的制定颇有争议,有人倾向传递 callback应该处于的位置,好在 wrap方法比较简单,可以比较容易修改代码)。看一个例子就能了解 wrap的用法:

var readFileSync = Future.wrap(require("fs").readFile);

Fiber(function () {

    var html = readFileSync("./1.txt").wait().toString();

    console.log(html);

}).run();

从这个例子中可以看出Fiber异步转同步确实非常有效,除了语法上多了一步 .wait()外,其他已经 fs提供的 fs.readFileSync方法别无二致了。

4.Future.wait(futures):

这个方法前面已经多次看到了。顾名思义,它的作用就是等待结果。如果要等待一个future的实例的结果,直接调用 futureInstance.wait()即可;如果需要等待一系列future实例的结果,则调用 Future.wait(futuresArray)。需要注意的是,在第二种用法中,一个future实例在运行时出现错误, wait方法不会抛出错误,不过我们可以使用 get()方法直接获取运行结果。

5.Future.prototype.get():

get()的用法与 wait()的第一种方式很像,所不同的是, get()立刻返回结果。如果数据没有准备好, get()会抛出错误。

6.Future.prototype.resolve(param1,param2):

上面的的 wrap方法总给人以一种 future其实在吞噬异步方法的回调函数,并直接返回异步结果。事实上 future也通过 resolve方法提供设置回调函数的解决方案。 resolve最多接受两个参数,如果只传入一个参数, future认为传了一个node风格的回调函数,例如如下示例:

futureInstance.resolve(function (err, data) {

    if (err) {

        throw  err;

    } else {

        console.log(data.toString());

    }

});

如果传入两个参数,则表示对错误和数据分别做处理,示例如下:

futureInstance.resolve(function (err) {

    throw err;

}, function (data) {

    console.log(data.toString());

});

另外 future并不区分 resolve的调用时机,如果数据没有准备好,则将回调函数压入队列,由 resolver()方法统一调度,否则直接取数据立即执行回调函数。

7.Future.prototype.isResolved():

返回布尔值,表示操作是否已经执行。

8.Future.prototype.proxy(futureInstance):

proxy方法提供一种 future实例的代理,本质上是对 resolve方法的包装,其实是将一个instance的回调方法作为另一个instance的回调执行者。例如:

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

虽然执行的是 proxy,但是最终 target的回调函数执行了,并且是以 proxy的执行结果驱动 target的回调函数。这种代理手段也许在我们的实际应用中有很大作用,我暂时还没有深入地思考过。

9.Future.prototype.return(value):

10.Future.prototype.throw(error):

11.Future.prototype.resolver():

12.Future.prototype.detach():

以上四个API呢我感觉相对于别的API,实际使用的场景或作用比较一般。 return和 throw都受 resolver方法调度,这三个方法都很重要,在正常的future使用流程中都会默默工作着,只是我没有想出具体单独使用它们的场景,所以没有办法具体介绍。 detach方法只能算 resolve方法的简化版,亦没有介绍的必要。

NodeJs 相关文章推荐
NodeJS框架Express的模板视图机制分析
Jul 19 NodeJs
NodeJS url验证(url-valid)的使用方法
Nov 18 NodeJs
nodejs之请求路由概述
Jul 05 NodeJs
Nodejs实现的一个简单udp广播服务器、客户端
Sep 25 NodeJs
详谈nodejs异步编程
Dec 04 NodeJs
轻松创建nodejs服务器(10):处理上传图片
Dec 18 NodeJs
Nodejs中的this详解
Mar 26 NodeJs
关于Mac下安装nodejs、npm和cnpm的教程
Apr 11 NodeJs
详解Nodejs内存治理
May 13 NodeJs
nodejs简单抓包工具使用详解
Aug 23 NodeJs
Nodejs中使用puppeteer控制浏览器中视频播放功能
Aug 26 NodeJs
nodejs实现百度舆情接口应用示例
Feb 07 NodeJs
nodeJS代码实现计算交社保是否合适
Mar 09 #NodeJs
Nodejs关于gzip/deflate压缩详解
Mar 04 #NodeJs
nodejs URL模块操作URL相关方法介绍
Mar 03 #NodeJs
Windows系统中安装nodejs图文教程
Feb 28 #NodeJs
NodeJS中利用Promise来封装异步函数
Feb 25 #NodeJs
NodeJS Web应用监听sock文件实例
Feb 18 #NodeJs
NodeJS使用jQuery选择器操作DOM
Feb 13 #NodeJs
You might like
谷歌音乐搜索栏的提示功能php修正代码
2011/05/09 PHP
php whois查询API制作方法
2011/06/23 PHP
PHP中__get()和__set()的用法实例详解
2013/06/04 PHP
PHP提取字符串中的手机号正则表达式怎么写
2017/07/17 PHP
JQuery datepicker 使用方法
2011/05/20 Javascript
Javascript/Jquery——简单定时器的多种实现方法
2013/07/03 Javascript
Select标签下拉列表二级联动级联实例代码
2014/02/07 Javascript
javascript:window.open弹出窗口的位置问题
2014/03/18 Javascript
js网页右下角提示框实例
2014/10/14 Javascript
JavaScript Sort 的一个错误用法示例
2015/03/20 Javascript
JS学习之表格的排序简单实例
2016/05/16 Javascript
JS绘制微信小程序画布时钟
2016/12/24 Javascript
vue快捷键与基础指令详解
2017/06/01 Javascript
jsonp跨域请求详解
2017/07/13 Javascript
jquery实现联想词搜索框和搜索结果分页的示例
2018/10/10 jQuery
通过npm或yarn自动生成vue组件的方法示例
2019/02/12 Javascript
微信小程序左滑删除实现代码实例
2019/09/16 Javascript
JQuery Ajax如何实现注册检测用户名
2020/09/25 jQuery
python 查找文件夹下所有文件 实现代码
2009/07/01 Python
python 快速排序代码
2009/11/23 Python
python使用mysql数据库示例代码
2017/05/21 Python
Python中装饰器学习总结
2018/02/10 Python
python按行读取文件,去掉每行的换行符\n的实例
2018/04/19 Python
python3 遍历删除特定后缀名文件的方法
2018/04/23 Python
python中验证码连通域分割的方法详解
2018/06/04 Python
Python Tkinter模块实现时钟功能应用示例
2018/07/23 Python
numpy添加新的维度:newaxis的方法
2018/08/02 Python
python 使用值来排序一个字典的方法
2018/11/16 Python
django 连接数据库出现1045错误的解决方式
2020/05/14 Python
自荐信如何“自荐”
2013/10/24 职场文书
优秀毕业生求职信范文
2014/01/02 职场文书
自我评价格式
2014/01/06 职场文书
庆祝教师节标语
2014/10/09 职场文书
综合实践活动报告
2015/02/05 职场文书
开网店计划分析
2019/07/30 职场文书
详解Laravel制作API接口
2021/05/31 PHP