Nodejs 利用libuv编写异步多线程的addon实例
Nodejs 利用libuv编写异步多线程的addon实例
最近cnode上很多TX在问关于node的异步回调以及单线程的事情,今天看了libuv的一些api和demo,自己简单写了一个利用libuv实现异步多线程的addon的例子,真心希望大牛指正啊。
demo例子的地址: https://github.com/DoubleSpout/libuv_ex 也可以
npm install libuv_ex
请保证您的node版本在0.10.x
首先介绍下libuv,libuv 是一个高性能事件驱动的程序库,封装了 Windows 和 Unix 平台一些底层特性,为开发者提供了统一的 API。libuv 采用了 异步 (asynchronous), 事件驱动 (event-driven)的编程风格, 其主要任务是为开人员提供了一套事件循环和基于I/O(或其他活动)通知的回调函数, libuv 提供了一套核心的工具集, 例如定时器, 非阻塞网络编程的支持, 异步访问文件系统, 子进程以及其他功能。 可见nodejs的一切异步操作都是基于libuv来实现的,有了它的这些api我们也就可以编写出异步的nodejs模块了。
最好结合github上的test和liuv.h来看,更加容易弄懂 中文版的libuv手册:http://forhappy.github.io/uvbook/index.html libuv项目地址:https://github.com/joyent/libuv
简单介绍一下这个demo实例把,我在demo里写了5个例子,分别用了不同的技术,执行了10次fibo(40),最后所得到的结果也各不相同。 c++代码都在 ./src 文件夹中,Asyn类中定义了下述5种不同情况的方法和一些libuv的api所需要的回调函数,在job类中,定义了执行fibo的任务函数,和一些相关设置,在线程中或者异步回调中传递的都是job类的指针。 例子执行代码如下:
//do fibo 10 times
var i = 40;
var times = 10;
asyn.sync(times,i,function(err, result){ //1、这里执行10次fibo(40)的函数,分别用不同的技术
console.log(‘fibo(’+i+’):’ + result)
})
var d1 = Date.now();
while(Date.now() - d1 <= 1000*1){ //2、这里将主线程sleep 1秒,模拟js的执行工作
//sleeping 10sec simulate the js work
}
我们分别以5种不同的方式执行10次fibo(40),看看测试结果会是什么样子的,我们主要记录全部执行任务的时间和模拟的js任务执行的时间。
1、完全同步,在c++代码中直接10次循环执行fibo(40) 执行测试代码: https://github.com/DoubleSpout/libuv_ex/blob/master/test/sync.js
main_thread_js_work: 4568ms
sync_all_the_work: 4568ms
可见js执行完毕和所有任务执行完成是同一时间的,这个结果合情合理
2、使用libuv创建同步线程来计算fibo,然后将结果回调主线程 执行测试代码: https://github.com/DoubleSpout/libuv_ex/blob/master/test/sync_thread.js
main_thread_js_work: 4595ms
sync_thread_all_the_work: 4595ms
由于整个程序都是同步的,所以和之前一样,同步的多线程没有明显提升结果
3、使用libuv的async异步回调方式,将所有任务注册到事件循环中,这样将先执行js的sleep,然后在主线程中分别计算fibo的结果,这种情况有点像我们使用process.nextTick来让一个计算异步执行,保证当前主线程的工作不被阻塞,最后进行耗时计算。 执行测试代码: https://github.com/DoubleSpout/libuv_ex/blob/master/test/asyn.js
main_thread_js_work: 1003ms
asyn_all_the_work: 4457ms
因为我们将fibo放在了异步去执行,所以js的任务将被先执行,js任务执行了1秒(我们模拟sleep了1秒)之后就是fibo的任务,总时间和上面两种情况大致相同。
4、使用libuv的async异步回调的方式,我们先注册fibo执行完毕的异步回调函数,然后创建异步的线程去执行fibo,这样js的任务将和fibo线程同时进行工作. 执行测试代码: https://github.com/DoubleSpout/libuv_ex/blob/master/test/asyn_thread.js
main_thread_js_work: 1017ms
asyn_thread_all_the_work: 1897ms
因为js任务抢占着主线程,同时也是因为js任务执行时间比较短,所以js任务结束提示先打印到了屏幕上,然后各异步线程也执行完毕,总耗时1897ms,比上面几种情况要快一倍,这主要归功于多核CPU的同时计算
5、使用libuv自带的线程池进行异步计算fibo,我们先向线程池注册工作回调,然后再注册主线程的完成回调 执行测试代码: https://github.com/DoubleSpout/libuv_ex/blob/master/test/asyn_pool.js
main_thread_js_work: 1002ms
asyn_pool_all_the_work: 2382ms
这种情况和上述情况一样,不过我们不是为每个计算生成一个线程,而是使用了libuv内部的线程池,所以制约了并行计算的能力,但是这样更加安全,不容易因为不可控的线程数量导致程序崩溃,事实上libuv为我们提供了4个线程的线程池。
最后总结一下: 使用libuv我们最好采用可控的异步线程配合异步回调来做一些事情,这样可以不阻塞主的js线程,还能并行执行任务,当任务结束后记得一定要回调主线程去执行js的回调函数,不能在其他线程去执行js的回调,因为在v8的一个isolate中,不可以多个线程同时操作一个isolate。当我们在线程中操作共享变量时记得加锁和解锁。在异步任务执行完毕后记得执行uv_close,将其关闭,同时因为我们都是将指针作为参数传递的,不要忘记delete掉之前new的指针,这样通过libuv的异步多线程api,我们就可以很轻松的为nodejs编写一些跨平台非阻塞扩展了。 PS,经过在win8 64位 和 linux 2.6.4 32bit虚拟机的测试中,我发现win8真是弱爆了,linux虚拟机执行上述任务的所消耗时间比我真机win8还要少一些。
博客原文地址: http://snoopyxdy.blog.163.com/blog/static/601174402013422103614385/
Nodejs 利用libuv编写异步多线程的addon实例
最近看到CNode社区上有不少关于Node.js异步回调及单线程问题的讨论,于是决定尝试使用libuv来实现一个异步多线程的addon例子。本文将介绍如何利用libuv来编写这样的addon,并展示几个不同的示例。
示例代码地址:
- GitHub仓库:libuv_ex
- 安装命令:
npm install libuv_ex
请确保你的Node.js版本在0.10.x以上。
libuv简介
libuv是一个高性能的事件驱动库,它封装了Windows和Unix平台的底层特性,为开发者提供统一的API。libuv采用异步和事件驱动的编程风格,主要功能包括事件循环、基于I/O或其它活动的通知回调、定时器、非阻塞网络编程支持、异步文件系统访问等。
demo实例介绍
在demo中,我编写了5个例子,分别使用不同的技术来执行10次fibo(40)的计算。所有的C++代码位于./src
文件夹中。Asyn
类中定义了5种不同情况的方法和一些libuv的回调函数。Job
类中定义了执行fibo任务的函数及相关设置。
示例代码
// do fibo 10 times
var i = 40;
var times = 10;
asyn.sync(times, i, function(err, result) { // 执行10次fibo(40)的函数
console.log('fibo(' + i + '):' + result);
});
var d1 = Date.now();
while (Date.now() - d1 <= 1000 * 1) { // 模拟JS的执行工作,休眠1秒
// sleeping 1 sec simulate the JS work
}
示例执行
以下是5种不同方式执行10次fibo(40)的示例:
-
完全同步
- 在C++代码中直接循环执行10次fibo(40)。
- 测试代码:sync.js
-
使用libuv创建同步线程
- 使用libuv创建同步线程来计算fibo,然后将结果回调主线程。
- 测试代码:sync_thread.js
-
使用libuv的async异步回调
- 将所有任务注册到事件循环中,先执行JS的sleep,再在主线程中计算fibo的结果。
- 测试代码:asyn.js
-
使用libuv的async异步回调和线程
- 先注册fibo执行完毕的异步回调函数,然后创建异步的线程去执行fibo。
- 测试代码:asyn_thread.js
-
使用libuv的线程池
- 向线程池注册工作回调,然后再注册主线程的完成回调。
- 测试代码:asyn_pool.js
总结
使用libuv我们可以采用可控的异步线程配合异步回调来做一些事情,这样可以不阻塞主的JS线程,还能并行执行任务。当任务结束后记得一定要回调主线程去执行JS的回调函数。在线程中操作共享变量时记得加锁和解锁。在异步任务执行完毕后记得执行uv_close
,将其关闭。通过libuv的异步多线程API,我们可以很方便地为Node.js编写一些跨平台非阻塞扩展。
经过在Win8 64位和Linux 2.6.4 32位虚拟机的测试,发现Linux虚拟机的执行效率更高。
先收藏了
膜拜
膜拜,未来N年都不会再碰C的撸过…最近一次碰C是高中了…
拾起来吧,玩nodejs真的需要c++知识
正开始看 addon. 楼主好文。
可能例子有问题的啊,多交流~
要使用libuv编写一个Node.js的异步多线程addon,需要了解libuv的基本概念,比如事件循环、异步回调和线程池等。这里我将简要说明如何实现一个简单的异步计算功能,例如计算斐波那契数列。
示例代码
C++部分 (addon.cc)
#include <node_api.h>
#include <stdlib.h>
#include <uv.h>
// 定义一个结构体用于线程间通信
struct FibData {
napi_env env;
napi_async_work work;
int n;
napi_value callback;
};
// 线程函数
static void WorkCallback(uv_work_t *req) {
FibData *data = static_cast<FibData*>(req->data);
int result = data->n <= 1 ? data->n : Fibonacci(data->n - 1) + Fibonacci(data->n - 2);
data->n = result;
}
// 回调函数
static void AfterCallback(uv_work_t *req, int status) {
FibData *data = static_cast<FibData*>(req->data);
napi_handle_scope scope;
napi_open_handle_scope(data->env, &scope);
napi_value callback;
napi_get_reference_value(data->env, data->callback, &callback);
napi_value argv[2];
argv[0] = nullptr; // 错误信息
napi_create_int32(data->env, data->n, &argv[1]);
napi_value global;
napi_get_global(data->env, &global);
napi_call_function(data->env, global, callback, 2, argv, nullptr);
napi_close_handle_scope(data->env, scope);
// 清理
napi_delete_async_work(data->env, data->work);
delete data;
delete req;
}
// 导出的API
napi_value CalculateFibonacci(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
if (argc != 1 || !napi_is_function(env, args[0])) {
napi_throw_error(env, nullptr, "Usage: CalculateFibonacci(callback)");
return nullptr;
}
int n;
NAPI_CALL(env, napi_get_value_int32(env, args[0], &n));
FibData *data = new FibData();
data->env = env;
data->n = n;
napi_create_reference(env, args[0], 1, &data->callback);
uv_work_t *request = new uv_work_t();
request->data = data;
napi_create_async_work(
env,
nullptr,
"CalculateFibonacci",
WorkCallback,
AfterCallback,
reinterpret_cast<void*>(data),
&data->work);
napi_queue_async_work(env, data->work);
napi_value result;
napi_get_null(env, &result);
return result;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, CalculateFibonacci)
JavaScript部分 (index.js)
const addon = require('./build/Release/addon');
function fibo(n, callback) {
addon.CalculateFibonacci(n, (err, result) => {
if (err) throw err;
console.log(`fibo(${n}) = ${result}`);
});
}
fibo(40, () => {});
解释
- C++代码:定义了一个
FibData
结构体来存储线程所需的数据,并实现了WorkCallback
和AfterCallback
两个回调函数。WorkCallback
在线程中执行计算,而AfterCallback
则负责将结果传回JavaScript层。 - JavaScript代码:加载addon模块,并定义了一个简单的
fibo
函数,该函数接受一个数字和一个回调,将计算结果返回给回调。
注意事项
- 使用libuv的线程池和异步回调机制可以有效地避免阻塞Node.js的主线程。
- 在线程间传递数据时要注意线程安全问题,如使用互斥锁保护共享资源。
- 确保在使用完异步工作后清理资源,包括删除引用和释放内存。
此示例仅用于演示目的,实际应用中可能需要更复杂的错误处理和性能优化。