nodejs中实现睡眠功能的例子
nodejs最让人不爽的是它的单线程特性,不能做很多事情,对于CPU密集型的场景不够强大。一直以来,我都想在javascript语言的框架下找到一些解决方案,来解决线程不可操作、性能不佳的问题。印象最深的方案是fibers,但fibers等方案在线程操作上还是比较笨拙,过于依赖辅助线程,本末倒置;就光纤而言,无法解决javascript固有的低性能问题;最尴尬的是,在javascript语言的框架下,线程之间的消息传输往往受限,往往无法真正共享对象。
Nodejs' addon模式无疑是优秀的,具有很强的灵活性、完整的功能和原生代码的性能。简单来说,nodejs直接调用c/c模块,是javascript和native的混合开发模式。好东西,为什么不用呢?Addon应该算是一个大话题,今天不想说太深,自己也没有太多练习。然后,实现一个睡眠功能,就像抛砖引玉一样。
睡眠
为什么javascript不能实现真正的睡眠?休眠方法是向操作系统内核注册一个信号,指定时间后发送唤醒信号,线程本身挂起。本质上,线程睡眠(1000)意味着告诉操作系统:不要给我分配1000毫秒的CPU时间。因此,睡眠可以保证线程在挂起时不再占用CPU资源。Javascript在单线程上运行,取消了线程的概念。自然,没有办法挂起主线程。
有人会尝试用javascript来实现睡眠,例如,复制代码如下:函数sleep(sleep time){ for(var start=new date;新的开始日期=睡眠时间;) { } }
这是用空循环阻断主进程实现睡眠,显然与真正的睡眠相去甚远。
如果真正的睡眠实现了呢?
环境准备
开发环境
正如我之前的一些博客所说,这里省略了:node.js npm、python 2.7、visual studio/x-code。
编译工具
编译工具需要使用node-gyp,nodejs的较新版本也附带了这个库。如果node-gyp没有附带,请执行:
复制代码如下:npm install -g node-gyp
我没有精力去研究gyp的特性。如果你熟悉gcc等编译器,不排除gyp不兼容,编译选项和开关不一样。建议重写nodejs的C代码。如果有需要重用的模块,可以考虑用熟悉的gcc编译成动态链接库,然后编写少量代码使用动态链接库,再用gyp编译这部分代码供nodejs使用。
进入项目文件夹并执行npm init来初始化项目。为了让nodejs知道我们要做addon,需要添加:复制代码如下:'gyp-file': true。
如果你用过gcc,你一定记得makefile。同样,gyp通过一个名为binding.gyp的文件来描述编译配置,这是一个我们非常熟悉的json文件。Gyp不是我们讨论的重点,所以binding.gyp就不深入探讨了。我们只关注一些最重要的配置项。下面是binding.gyp文件的一个简单但完整的例子:复制代码如下: { ' targets ' :[{ ' target _ name ' : ' hello ',' sources' : ['hello.cc'],' include _ dirs ' :['](node-e ' require(' nan ' \ '))')]}
只需看看这里涉及的三个配置项目:
1.target_name:指示输出模块名称。2.sources:指示要编译的源代码路径,它是一个数组。3.include_dirs:指示编译过程中要使用的目录,这些目录中的头文件可以在预编译指令#include中搜索。本文采用了一种特殊的写法,不是把路径给定为字符串常量,而是运行一个命令节点-e 'require('nan '),我们将在nan库之后再谈。我们先来看看命令输出了什么:node _ modules \ n。最初,这个命令意味着返回nan库的路径。
c代码
好的,既然源代码已经被配置为hello.cc,那么就创建这样一个文件。有一个问题需要提前提醒。我们写的C模块最终会被v8引擎使用,所以api和编写都受到v8引擎的限制。但是不同版本的nodejs实际上采用了不同版本的v8引擎,这意味着用一组C代码很难满足不同版本的nodejs(指编译过程,编译后应该跨版本可用,无需验证。Github无法上传二进制类库,所以github上的开源会带来麻烦。Npm可以直接上传二进制类库,跳过编译步骤,所以问题比较小。
节点0.11及以上:复制代码如下: # include . h # include ev8 . h
使用命名空间V8;
void sleep func(const v 8:3360 functioncallbackinfovalue参数){ Isolate * Isolate=Isolate : getcurrent();HandleScope scope(隔离);double arg 0=args[0]-NumberVaLue();睡眠(arg 0);}
void Init(HandleObject导出){ Isolate * Isolate=Isolate :3360 getcurrent();导出-设置(字符串String:NewFromUtf8(隔离,'睡眠'),函数模板:New(隔离,睡眠函数)-GetFunction());}
NODE_MODULE(你好,Init);
节点0.10及以下:
复制代码如下: # include . h # include V8 . h
使用命名空间V8;
handlevaluesleep fun(const Arguments args){ HandleScope作用域;double arg 0=args[0]-NumberVaLue();睡眠(arg 0);返回范围。关闭(未定义());}
void Init(HandleObject导出){ exports-Set(string :3360 newsymbol(' sleep '),function template : new(sleep fun)-getfun());}
NODE_MODULE(你好,Init);
可以看出变化还是挺大的。如果我们能屏蔽这些差异,那就太好了。有办法吗?我写这么多只是想告诉你有一种方法。是时候邀请南京图书馆了。
圆盘烤饼
请记住,在binding.gyp中,我们引入了nan library的路径,将在这里使用。南京图书馆是做什么的?它提供了一个抽象层,屏蔽了nodejs 0.8、nodejs 0.10、nodejs 0.12和io.js Awesome之前的addon的语法差异!
第一次安装:npm install - save nan,看同样的功能,使用nan后如何实现:复制代码如下: # includenan.h使用命名空间V8;
NAN_METHOD(睡眠){ NanScope();double arg 0=args[0]-NumberVaLue();睡眠(arg 0);NanReturnUndefined();}
void Init(HandleObject导出){ exports-Set(NanSymbol('sleep '),function template : new(Sleep)-GetFunction());}
NODE_MODULE(你好,Init);
你需要知道的是nan,但不需要关注v8。
从下往上看:复制代码如下:NODE_MODULE(你好,Init);
这句话定义了addon的入口。请注意,第一个参数应该与我们在binding.gyp中的target_name项一致。第二个参数是addon的entry函数。复制代码如下: void init(句柄对象导出){exports-set (nansymbol ('sleep '),函数模板:3360 new(sleep)-getfunction());}
这个代码是addon的入口方法。它接收两个参数,导出和模块。上面的例子省略了第二个参数。如果模块提供了对象,可以直接指定键值;用于出口,如示例中的出口;如果是特殊的,只提供一个值或者一个函数,那么需要第二个参数,类似于Node _ Set _ Method(模块,‘exports’,foo)。在这个例子中,它意味着输出这样一个模块:
复制代码如下:{ 'sleep': Sleep}
睡眠是一种功能。我们来看看Sleep的定义:复制代码如下: nan _ method(Sleep){ nanscope();double arg 0=args[0]-NumberVaLue();睡眠(arg 0);NanReturnUndefined();}
其实就是读取javascript传入的参数,转换成double,然后调用C的sleep方法.
编译插件
下面将要开始编译这个模块。首先,执行node-gyp configure为构建做准备,这将生成一个构建文件夹和一些文件。接下来,运行node-gyp构建,就可以开始编译了。在这个例子中,hello.node文件将在/build/Release/目录中生成,这是最后可以被javascript引用的插件模块。
如果稍后修改了C代码,则不需要运行node-gyp configure,只需运行node-gyp build即可。
Nodejs使用
创建一个index.js看看如何使用这个模块:复制代码如下: varsleep=require('。/build/release/hello.node ')。睡觉;
控制台日志(新日期);睡眠(1000);控制台日志(新日期);
//结果//wed mar 04 2015 14:55:18 GMT 0800(中国标准时间)//wed mar 04 2015 14:55336019 GMT 0800(中国标准时间)
很简单,不是吗?它和普通的javascript函数完全一样。
至此,本文想要分享的技术要点已经阐述完毕。但是.和一开始提供的方法相比有什么区别?我就不截图了,只是说明一下结果:
因为插件模式下采用的方法是线程挂起,理论上不会有CPU占用和内存变化,结果也验证了这一点。看看javascript循环模拟睡眠的方式,因为它一直在运行循环,内存增加一点也可以理解,没什么大不了的;看着CPU占据25%,好像还可以。真的是这样吗?是时候揭露真相了。我测试的笔记本电脑的CPU是双核四线程,合计占用CPU 25%.这个睡眠占用了双核和四个线程中的一个吗?其实我发现这期间没有线程被锁定,但不是javascript,而是英特尔超线程。因为说是四个线程,其实本质是两个处理核心只能是双线程,但是cpu在切时间片上做了一个小把戏。例如,核心cpu01被分为t0和t2。假设任务将在n个刻度之后的一个刻度中被划分为t0,则任务将在下一个刻度中被划分为t2。因此,从相对较长的时间尺度来看(相对于调度周期),t0和t2上任务的运行时间基本相等。因此,呈现的场景是nodejs的进程不占用t0或t2到100%,而是分别占用50%左右。由于windows的进程调度相对复杂,CPU使用率波动较大。可以预测,如果脚本由双核、双线程的CPU处理,CPU占用率会上升到50%,一核卡住。如果是单核CPU,CPU会突然上升到100%。
好像CPU说的有点多,超线程也是猜测,看看就好。
版权声明:nodejs中实现睡眠功能的例子是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。