nodejs中实现sleep功能实例
nodejs最让人不爽的就是其单线程特性,很多事情没法做,对CPU密集型的场景,性能也不够强劲。很长一段时间,我想在javascript语言框架下寻求一些解决方案,解决无法操作线程、性能差的问题。曾经最让我印象深刻的方案是fibers,不过fibers也好,其他方案也好,在线程操作上还是很别扭,太过依赖辅助线程,本末倒置;就fiber而言,javascript固有的低性能问题并不能解决;最别扭的是在javascript语言框架下,线程间的消息传递常常很受限制,经常无法真正地共享对象。
nodejs的addon方式无疑是极好的,具有极强的灵活性、完备的功能和原生代码的性能。简单说就是让nodejs直接调用c/c++模块,是一种javascript和native的混合开发模式。好东西呀,为什么不用呢?addon应该算是一个大话题,今天我也不想太深入说这个,我自己的实践也不是很多。那就实现一个sleep函数,就当是抛砖引玉吧。
sleep
为什么javascript实现不了真正的sleep?sleep方法是通过向操作系统内核注册一个信号,指定时间后发送唤醒信号,而线程本身则挂起。本质上当线程sleep(1000)代表告诉操作系统:1000ms内不要给我分配CPU时间。所以sleep能保证线程挂起时不再占用CPU资源。而javascript是单线程运行,本身取消了线程的概念,自然没有办法将主线程挂起中断。
也有人会尝试用javascript方法要实现sleep,例如这样:
functionsleep(sleepTime){ for(varstart=+newDate;+newDate-start<=sleepTime;){} }
这是采用空循环阻塞住主进程的运行来实现sleep,明显跟真正的sleep相去甚远。
那么如果实现一个真正的sleep呢?
环境准备
开发环境
之前我的一些博客已经说过,这里从略:node.js+npm、python2.7、visualstudio/x-code。
编译工具
编译工具需要采用node-gyp,较新版本的nodejs自带此库,如果没有自带node-gyp,请执行:
npminstall-gnode-gyp
gyp特性我没有精力去研究,如果你比较熟悉gcc等其他编译器,不排除gyp会有不兼容之处,而且编译选项和开关也是不尽相同。建议针对nodejs重新编写c++代码,如果确实有模块需要复用,可以考虑先用熟悉的gcc编译成动态链接库,再编写少量代码来使用动态链接库,再把这部分代码用gyp编译出来供nodejs使用。
进入项目文件夹,执行npminit初始化项目。为了让nodejs知道我们想制作addon,我们需要在package.json中添加:
"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搜索到。在这里使用了一个比较特殊的写法,没有把路径用字符串常量给出,而是运行一个命令node-e"require('nan')",nan库后面再说,先看看这个命令输出什么:node_modules\nan,原来这句命令的意思是返回nan库的路径。
C++编码
OK,既然已经配置了源代码是hello.cc,那就建立一个这样的文件。有一个问题需要提前提醒大家,我们所写的c++模块最终是要被v8引擎使用,所以api、写法等受到v8引擎的制约。而不同版本的nodejs其实采用的v8引擎的版本也不尽相同,这也就意味着很难用一套c++代码满足不同版本的nodejs(指编译过程,编译完成后跨版本应该能够使用,没有验证过。github不能上传二进制类库,所以github上开源会有麻烦。npm可以直接上传二进制类库,跳过编译步骤,所以问题相对较小)。
node0.11及以上版本:
#include<node.h> #include<v8.h>
usingnamespacev8;
voidSleepFunc(constv8::FunctionCallbackInfo<Value>&args){ Isolate*isolate=Isolate::GetCurrent(); HandleScopescope(isolate); doublearg0=args[0]->NumberValue(); Sleep(arg0); }
voidInit(Handle<Object>exports){ Isolate*isolate=Isolate::GetCurrent(); exports->Set(String::NewFromUtf8(isolate,"sleep"), FunctionTemplate::New(isolate,SleepFunc)->GetFunction()); }
NODE_MODULE(hello,Init);