Nodejs多线程,真正的非阻塞
Nodejs多线程,真正的非阻塞
node从他推出至今,充满赞美和饱受诟病的都是其单线程模型,所有的任务都在一个线程中完成(I/O等例外),优势的地方自然是免去了频繁切换线程的开销,以及减少资源互抢的问题等等,但是当nodejs面对cpu密集型模型的时候就力不从心了。尽管node拥有异步机制,可以把一些耗时算法丢入eventloop等待下个事件循环再做,但是因为其任然是单线程模型,所以终究会造成阻塞。
先解释一下两个名词,Fibers 和 Threads。 Fibers 又称纤程,可以理解为协同程序,类似py和lua都有这样的模型。使用Fibers可以避免对资源的互抢,减少cpu和内存的消耗,但是Fibers并不能够真正的并行执行,同一时刻只有一个Fibers在执行,如果在其中一个Fibers中执行过多的cpu操作或者写了个死循环,则整个主程序将卡死住。node中的异步事件循环模型就有点象这个。
Threads 又称线程,他可以在同一时刻并行的执行,他们共享主进程的内存,在其中某一时刻某一个threads锁死了,是不会影响主线程以及其他线程的执行。但是为了实现这个模型,我们不得不消耗更多的内存和cpu为线程切换的开销,同时也存在可能多个线程对同一内存单元进行读写而造成程序崩溃的问题。
很多让node支持多线程的方法是使用c/c++的addon来实现,在需要进行cpu密集型计算的地方,把js代码改写成c/c++代码,但是如果开发人员对c++不是很熟悉,一来开发效率会降低不少,二来也容易出bug,而且我们知道在addon中的c++代码除了编译出错外,是很难调试的,毕竟没有vs调试c++代码方便。
令人振奋的消息,我们为什么不让node也支持多线程模型呢?于是Jorge为我们开发出了一个让node支持多线程模型的模块:threads_a_gogo github地址:https://github.com/xk/node-threads-a-gogo
有了threads-a-gogo(以下简称TAGG)这个模块之后,我们可以让node做更多的事情,我记得以前我看过一篇文章,说node只能应付i/o密集型场景,在cpu密集型场景将完败给apache,因为apache是为每一个请求起一条线程的,所以在处理cpu密集型任务时一个线程的高强度计算不会很大程度的影响其他线程,类似的还有php的fastcgi,这也是很多拿node和php进行比较时,php的拥护者们一直提出的理论。
我们先来做一个简单的测试,用我们suqian大大最喜欢的斐波那契数组来看一下,加入了多线程的node有多么的强悍:(测试机器为4CPU) 没有使用TAGG的正常情况,异步也帮不了我们应对cpu密集型任务
function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; } var n=8 function back(){ if(!–n) return console.timeEnd(‘no thread’); } console.time(‘no thread’);
process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); })
process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); })
我们模拟了8个异步的行为,测试用的node v0.8.16版本,所以 process.nextTick还是异步方法。最后我们输出结果为: 165580141 165580141 165580141 165580141 165580141 165580141 165580141 165580141 no thread: 23346ms 接下来我们使用TAGG模块来测试同样的执行8次斐波那契数组计算,看看成绩如何? function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; } console.time(‘8 thread’); var numThreads= 8; //创建线程池,最大数为8 var threadPool= require(‘threads_a_gogo’).createPool(numThreads).all.eval(fibo); //为线程池注册程序 var i=8; var cb = function(err,data){ //注册线程执行完毕的回调函数 console.log(data); if(!–i){ threadPool.destroy(); console.timeEnd(‘8 thread’); } } threadPool.any.eval(‘fibo(40)’, cb); //开始向线程池中执行fibo(40)这个任务
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
threadPool.any.eval(‘fibo(40)’, cb);
最重的结果: 165580141 165580141 165580141 165580141 165580141 165580141 165580141 165580141 8 thread: 9510ms 相比不使用多线程模型的node,使用了TAGG模块之后,我们在4CPU服务器上的测试结果要快上一倍还不止。 到这里我们看上去找到了一个比较完美的解决方案应对CPU密集型任务,但是可能有同学会说,我可以使用cluster来做相同的事情,下面我们来做一个使用cluster计算这些任务的情况: var cluster = require(‘cluster’); var numCPUs = 8; function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; } console.time(‘8 cluster’); if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } var i = 8; cluster.on(‘exit’, function(worker, code, signal) { if(!–i){ console.timeEnd(‘8 cluster’); process.exit(0); } }); } else { console.log(fibo (40)); process.exit(0); } 代码上的复杂程度比使用TAGG要高的多,而且如果是动态计算斐波那契数组的结果,编码将更加困难,需要在fork时挂上不同的参数,出错的几率也更大。同时还有更重要的一个事情,如果是创建一个http服务器,如果4个cluster都在计算fibo,那第5个请求node将无法处理,而是用TAGG则还是能够正常处理的,所以cluster并不能解决单线程模型的cpu密集计算带来的阻塞问题,我们看下测试结果: 165580141 165580141 165580141 165580141 165580141 165580141 165580141 165580141 8 cluster: 11925ms
TAGG模块还有其他更多的功能,比如事件触发,平滑退出,查看线程工作状态等等,总之TAGG模块给node注入了新的活力,让node一直饱受诟病的处理cpu密集任务问题得到了一个妥善的解决,就算你不擅长c++代码,也能够轻松编写出多线程的真正的非阻塞node程序了。
Nodejs多线程,真正的非阻塞
引言
自从Node.js推出以来,它以其单线程、事件驱动的模型受到了广泛的赞誉和批评。单线程模型的优势在于减少了线程切换的开销,并且避免了资源竞争的问题。然而,当遇到CPU密集型任务时,Node.js的表现并不尽如人意。尽管Node.js拥有异步机制,但其单线程模型依然会导致阻塞。
理解Fibers和Threads
在深入探讨Node.js的多线程模型之前,我们需要了解两个重要的概念:Fibers和Threads。
-
Fibers:又称纤程,是一种轻量级的协作式多任务处理方式。类似于Python和Lua中的协程。使用Fibers可以避免资源竞争,减少CPU和内存消耗,但它们不能真正并行执行。如果一个Fiber长时间占用CPU或陷入死循环,整个应用程序会被阻塞。
-
Threads:又称线程,可以在同一时刻并行执行。它们共享主进程的内存空间。虽然线程可以提高并发性能,但也带来了更多的内存和CPU开销,以及潜在的数据竞争问题。
解决方案:threads_a_gogo模块
许多开发者尝试通过使用C/C++的addon来改进Node.js的多线程能力,但这通常需要较高的技术门槛。幸运的是,Jorge开发了一个名为threads_a_gogo
的模块,使得Node.js也能支持多线程模型。
threads_a_gogo
模块提供了一种更简单的方式来实现多线程编程,而无需深入了解底层的C/C++代码。这使得开发人员能够更轻松地编写多线程的非阻塞Node.js程序。
示例代码
让我们通过一个简单的例子来展示如何使用threads_a_gogo
模块来处理CPU密集型任务。我们将使用斐波那契数列计算作为示例。
const threads = require('threads_a_gogo').createPool(8);
function fibo(n) {
return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
console.time('8 thread');
const numThreads = 8;
const threadPool = threads.all.eval(fibo);
let i = 8;
const cb = (err, data) => {
console.log(data);
if (!--i) {
threadPool.destroy();
console.timeEnd('8 thread');
}
};
for (let j = 0; j < 8; j++) {
threadPool.any.eval('fibo(40)', cb);
}
在这个示例中,我们首先创建了一个包含8个线程的线程池。然后,我们定义了一个递归的斐波那契函数fibo
。接着,我们使用threads_a_gogo
模块的线程池来执行8次斐波那契数列的计算。每个线程计算一次fibo(40)
的结果,并通过回调函数cb
输出结果。最后,我们通过console.time
和console.timeEnd
来测量整个过程的时间。
测试结果
通过对比使用threads_a_gogo
模块与不使用多线程模型的情况,我们可以看到显著的性能提升。例如,在一台4核CPU的机器上,使用多线程模型后,计算时间从23秒减少到9秒多,提升了近一倍的性能。
结论
通过引入threads_a_gogo
模块,Node.js开发者可以更轻松地处理CPU密集型任务,从而克服单线程模型的局限性。这不仅提高了程序的性能,还降低了开发难度,使得Node.js在更多场景下都能表现出色。
这篇文章是不是发过了,好熟悉。
Node.js 的单线程模型使其在处理 I/O 密集型任务时表现出色,但在处理 CPU 密集型任务时可能会出现性能瓶颈。虽然 Node.js 提供了异步机制,但这些机制并不能完全避免阻塞问题。为了应对这个问题,可以使用多线程模型来提高性能。
threads_a_gogo
模块提供了一种在 Node.js 中实现多线程的方式,它可以帮助我们在处理 CPU 密集型任务时避免阻塞问题。以下是一个简单的示例,展示如何使用 threads_a_gogo
模块来执行 CPU 密集型任务:
const threads = require('threads_a_gogo');
// 创建一个线程池
const pool = threads.createPool(4); // 创建一个包含4个线程的池
// 定义一个 CPU 密集型任务函数
function fibo(n) {
return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
// 注册一个回调函数,用于处理线程返回的结果
const cb = function(err, data) {
if (err) throw err;
console.log(`Result: ${data}`);
};
// 将任务提交到线程池
pool.any.eval('fibo(40)', cb);
pool.any.eval('fibo(40)', cb);
pool.any.eval('fibo(40)', cb);
pool.any.eval('fibo(40)', cb);
// 等待所有任务完成
setTimeout(() => {
pool.destroy();
}, 5000);
在这个示例中,我们创建了一个包含4个线程的线程池,并将一个 CPU 密集型任务(计算斐波那契数列)提交给线程池。通过这种方式,我们可以在多个线程中并发地执行这些任务,从而避免阻塞问题。
threads_a_gogo
模块使得在 Node.js 中实现多线程变得简单且高效,即使开发者不熟悉 C++ 编程也能轻松地编写多线程的非阻塞代码。