由NodeJieba谈谈Node.js异步实现
有些人刚接触 Node.js 的时候,都会以为异步是多么高深莫测的技术。所以在此结合 NodeJieba 代码(加上详细注释)说说 Node.js 异步实现原理,你就会发现异步其实原理很通俗易懂。
示例代码来自 mix_segment.cpp 和 mix_segment.h 。
在 NodeJieba 里面有个异步调用函数 cut(arg1, arg2) ,arg1 是一个待切词的字符串,arg2 就是切完词后调用的回调函数。
NAN_METHOD (cut) {
NanScope(); // 不用管这行
if (args.Length() == 2){ // 检查参数个数是否正确。
string inputStr = ValueToString(args[0]); // 参数1是是待切词的字符串
Local<Function> callback = args[1].As<Function>(); // 参数2是回调函数
NanCallback* nanCallback = new NanCallback(callback); // 格式转换,不用管。
// 重点是以下两行,下文详细分析。
CutWorker* worker = new CutWorker(nanCallback, inputStr);
NanAsyncQueueWorker(worker);
}
else {
NanThrowTypeError("argc must equals to 2");
}
NanReturnUndefined();
}
整个过程的重点是以下两行:
CutWorker* worker = new CutWorker(nanCallback, inputStr);
NanAsyncQueueWorker(worker);
先说说 CutWorker 是什么?以下是 CutWorker 的类定义
class CutWorker : public NanAsyncWorker {
public:
// 注意到构造函数主要初始化两个私有参数。
CutWorker(NanCallback *callback, const string& s)
: NanAsyncWorker(callback), inputStr(s) {}
~CutWorker() {}
// 真正的执行函数
void Execute () {
segment.cut(inputStr, outputWords);
}
// 当 Execute 被执行之后,
// HandleOKCallback 会被自动调用。
void HandleOKCallback () {
NanScope();
Local<Value> args[1];
Local<Array> wordList;
WrapVector(outputWords, wordList);
args[0] = wordList;
callback->Call(1, args);
}
private:
// 注意,这两个私有参数不能是引用类型。
string inputStr;
vector<string> outputWords;
};
CutWorker 的定义非常简单,核心函数就是两个,Execute 和 HandleOKCallback 。然后再返回看刚才那两句代码:
CutWorker* worker = new CutWorker(nanCallback, inputStr);
NanAsyncQueueWorker(worker);
CutWorker 被初始化后,只是被扔进 NanAsyncQueueWorker 之后就不管了。而实际上发生的时候是,很一个后台线程,一直在等待 NanAsyncQueueWorker 这个队列。当队列有 Worker 被 Push 进去的时候,后台线程则会将 Worker 从队列中 Pop 出来,然后先后调用 Execute 和 HandleOKCallback 这两个函数。(这里说的先后只是顺序,不一定紧挨着,具体得看 Node.js 后台线程的源码实现。)
所以在 Execute 函数里面写上真正的切词过程。切词的结果存在 CutWorker 的私有变量 outputWords 里面。然后在 HandleOKCallback 执行回调函数,将私有变量 outputWords 作为回调函数的参数。
这样的过程就实现了在调用 cut 函数的时候,是异步非阻塞的,因为调用过程中只是将参数暂存在 CutWorker 这个类的私有变量成员里。实际的执行过程是在后台线程中执行。所以主线程是非阻塞的,而后台线程是阻塞型的,当队列为空的时候,后台线程一直阻塞等待。
【还有一个有助于理解异步的问题】
Q: 为什么 CutWorker 的两个私有参数不能是引用类型。A: 因为这两个私有参数是在构造函数被调用的时候被初始化的,在 CutWorker 被构造的时候,传入参数有可能是一个栈上的临时变量。而且 CutWorker 的私有参数其实在整个异步调用过程中,是作为状态保持者:待分词的句子,分词后的结果,都是存在私有变量中。所以只要这个 CutWorker 没有被析构。这些状态就一直被保持者。才能做到无论何时这个 CutWorker 被后台线程执行。都能保证参数有效。哪怕这些参数在主线程里面早就被析构了。
其实写过线程池的人就一眼就看明白了,线程池也是类似这样的原因 。都是需要使用 Worker 类的私有成员变量来保持着当时传入的参数。
【总结】
其实异步的实现原理非常的简单,但是总是会有一些不明真相的人觉得异步是多么高深的技术,然后不明觉厉,而且如教徒般迷信异步。其实说白了就是两个核心问题:
一是状态的保持,
二是后台线程对任务队列的悄悄执行。
建议继续学习:
- 关于IO的同步,异步,阻塞,非阻塞 (阅读:14455)
- fsockopen 异步处理 (阅读:9016)
- 配合jquery实现异步加载页面元素 (阅读:5367)
- 使用django+celery+RabbitMQ实现异步执行 (阅读:5005)
- Node.js 给前端带来了什么 (阅读:4546)
- 利用node.js搭建SPDY协议的翻墙服务 (阅读:3840)
- 异步编程与响应式框架 (阅读:3890)
- 多核与异步并行 (阅读:3862)
- redis源代码分析 - event library (阅读:3159)
- Google Analytics 异步代码详解 (阅读:3139)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:YanyiWu 来源: YanyiWu
- 标签: Node.js NodeJieba 异步
- 发布时间:2016-02-13 23:39:24
- [68] Go Reflect 性能
- [68] 如何拿下简短的域名
- [67] Oracle MTS模式下 进程地址与会话信
- [62] IOS安全–浅谈关于IOS加固的几种方法
- [61] 图书馆的世界纪录
- [60] 【社会化设计】自我(self)部分――欢迎区
- [58] android 开发入门
- [56] 视觉调整-设计师 vs. 逻辑
- [49] 给自己的字体课(一)——英文字体基础
- [48] 读书笔记-壹百度:百度十年千倍的29条法则