干货:收藏: http://cnodejs.org/topic/518b679763e9f8a5424406e9
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地址:
有了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=8function 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还是异步方法。最后我们输出结果为:
165580141165580141165580141165580141165580141165580141165580141165580141no thread:23346ms
接下来我们使用TAGG模块来测试同样的执行8次斐波那契数组计算,看看成绩如何?
function fibo (n){return n >1? fibo(n -1)+ fibo(n -2):1;}
console.time('8 thread');var numThreads=8;//创建线程池,最大数为8var 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);
最重的结果:
1655801411655801411655801411655801411655801411655801411655801411655801418 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密集计算带来的阻塞问题,我们看下测试结果:
1655801411655801411655801411655801411655801411655801411655801411655801418 cluster:11925ms
TAGG模块还有其他更多的功能,比如事件触发,平滑退出,查看线程工作状态等等,总之TAGG模块给node注入了新的活力,让node一直饱受诟病的处理cpu密集任务问题得到了一个妥善的解决,就算你不擅长c++代码,也能够轻松编写出多线程的真正的非阻塞node程序了。
最后分享一篇干货文章,相当很精彩的一篇博客:
tagg2,nodejs多线程模块,更好的api,支持nodejs原生模块,跨平台支持,windows,linux和mac
相关推荐
Node.js作为一个基于Chrome V8引擎的JavaScript运行环境,主要特点是基于事件循环的非阻塞I/O模型,虽然这样的设计让它在I/O密集型任务中表现优异,但其单线程模型却限制了在CPU密集型任务上的性能表现。为了解决这...
对于I/O密集型的操作,使用异步操作可以有效提升性能,而CPU密集型的操作,如果不希望阻塞事件循环,也应该采用异步或非阻塞的方式处理,或者考虑将其迁移到多线程或集群环境中运行。 在实际开发中,推荐在处理异步...
- **非阻塞I/O的优势**:非阻塞I/O机制使得NodeJS能够在不等待I/O操作完成的情况下继续执行其他任务,从而提高了整体的响应速度。 - **长连接的支持**:NodeJS能够很好地支持长连接,这对于实时通信应用非常重要。 ...
3. **非阻塞I/O**:非阻塞I/O模型使得NodeJS在处理I/O密集型任务时特别高效。这意味着当一个I/O操作被发起时,NodeJS不会等待结果返回,而是继续执行其他任务。 4. **轻量且高效**:因为NodeJS采用了非阻塞I/O模型...
NodeJS的核心是事件驱动、非阻塞I/O模型,这使得它在处理高并发请求时表现优秀。其单线程执行和异步编程的特点,让开发者能够构建高效的网络应用程序。 1. 安装与环境配置:NodeJS的安装过程相对简单,可以在官网...
除了同步 API 之外,该包还提供异步 API,它允许您构建非阻塞和多线程计算机视觉任务。 opencv4nodejs 支持 OpenCV 3 和 OpenCV 4。 该项目的最终目标是提供与 OpenCV API 和 OpenCV-contrib 模块的 Nodejs 绑定的...
- **非阻塞 I/O 示例**:传统的阻塞式 I/O 代码会导致整个操作系统线程停滞不前,而 Node.js 中的非阻塞 I/O 代码(例如使用回调函数)可以让程序立即返回到事件循环中继续执行其他任务。 - **查询数据库示例**: -...
- **事件机制**:NodeJS采用了一种非阻塞I/O模型,这意味着它可以高效地处理大量并发连接。当一个I/O操作发起后,NodeJS会继续执行其他任务,而不会等待该操作完成。 - **异步I/O模型**:这一模型使NodeJS能够有效...
单线程意味着所有操作都在同一执行线程上进行,但通过事件循环机制,NodeJS可以处理大量并发请求,利用非阻塞I/O实现高效运行。回调函数是NodeJS处理异步操作的主要方式,但同时也带来了回调地狱的问题,为解决这个...
事件驱动编程是NodeJS的一大特点,它使开发者能够编写出高效的、非阻塞的代码,而无需直接处理多线程的复杂性。这种编程范式更符合现实世界中的并发情况,例如在上面提到的家庭主妇的例子中,她可以同时准备黄瓜和...
Node.js采用事件驱动、非阻塞I/O模型,使其轻量又高效,尤其适合处理高并发的网络应用。Node.js的核心特性包括: 1. **事件驱动**:Node.js使用事件循环来处理并发请求,当一个任务完成时,会触发相应的事件,这样...
4. **非阻塞I/O**:非阻塞I/O 模型使得 NodeJS 可以高效地处理大量并发请求,减少资源消耗。 5. **轻量级与可伸缩**:适合构建从小规模到大规模的分布式应用。 6. **单进程、单线程**:尽管 NodeJS 默认使用单线程,...
2. **非阻塞I/O**:Node.js使用单线程模型处理I/O操作,通过异步回调和事件循环来实现高效的并发处理。这种模式非常适合处理大量的并发连接,如构建高并发的Web服务器。 3. **模块系统**:Node.js的模块系统是基于...
这种方式避免了传统的多线程同步 I/O 操作可能导致的阻塞问题,提高了系统资源的利用率。 2. **模块系统**:Node.js 使用 CommonJS 规范实现模块化,通过 `require` 函数加载模块,`exports` 或 `module.exports` ...
2. **非阻塞I/O**:Node.js的I/O操作都是异步的,避免了传统同步I/O可能导致的性能瓶颈,使得处理大量并发请求成为可能。 3. **单线程模型**:尽管Node.js在底层使用多线程处理某些任务,但对外呈现的是一个单线程...
首先,Node.js的核心特性之一是它的非阻塞I/O模型,这使得它在处理高并发场景时表现出色。它使用事件驱动编程,通过事件循环和回调函数来处理异步操作,避免了传统多线程编程中的上下文切换开销。 其次,Node.js...
2. **单线程**: 与传统的多线程模型不同,NodeJS采用事件驱动和非阻塞I/O模型,主要运行在一个单独的线程中。这减少了线程切换带来的开销,提高了系统效率。尽管是单线程,但NodeJS通过事件循环和回调函数处理并发,...
- **无锁机制**:由于Node.js采用了单线程模型,因此不存在多线程间的锁竞争问题,从而避免了死锁的发生。 ### Node.js的应用场景 Node.js非常适合处理I/O密集型任务,尤其在面对数万级别的并发连接时表现优异。...