NodeJs中的非阻塞方法介绍

首先我们利用NodeJs先构建一个基本的服务器。
index.js


代码如下:

var requestHandler = require("./requestHandler");
var server = require("./server");
var route = {
"/hello": requestHandler.hello,
"/upload": requestHandler.upload
};
server.start(route);

server.js


代码如下:

server.js


代码如下:

var http = require("http");
var url = require("url");
exports.start = function(route) {
var server = http.createServer(function(req, res) {
var pathName = url.parse(req.url).pathname;
var handler = route[pathName];
if (handler) {
console.log("Through path:" + pathName + ":" + new Date().getTime());
handler(res);
} else {
res.writeHead(404, {"Content-Type": "text/plain"});
res.end();
}
});
server.listen(8088);
};

requestHandler.js


代码如下:

exports.hello = function(res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("say hello.");
res.end();
};
exports.upload = function(res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("upload");
res.end();
};

在cmd中,键入node index.js即可启动。
但是,上面的代码是阻塞的。如果在createServer的回调函数中,有花费长时间的计算。那么会阻塞node.js的事件轮询。
NodeJS中,他的高效,关键在于快速的返回事件循环。
我们将requestHandler.js改造如下,在这个例子中,由于事件循环一直被sleep函数阻塞着,导致createServer的callback无法及时返回。


代码如下:

function sleep(milliSecond) {
var startTime = new Date().getTime();
console.log(startTime);
while(new Date().getTime() <= milliSecond + startTime) {
}
console.log(new Date().getTime());
}
exports.hello = function(res) {
sleep(20000);
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("say hello.");
res.end();
};
exports.upload = function(res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("upload");
res.end();
};

那么先键入http://localhost:8088/hello,后键入http://localhost:8088/upload。你会发现,upload虽然不需要花费太多时间,但是却要等到hello完成。
我们试图找寻异步调用的方法。比如formidable中的上传,经测试是非阻塞的。查看formidable的源码,发现最关键的是下面的代码:


代码如下:

IncomingForm.prototype.parse = function(req, cb) {
this.pause = function() {
try {
req.pause();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
};
this.resume = function() {
try {
req.resume();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
};
this.writeHeaders(req.headers);
var self = this;
req
.on('error', function(err) {
self._error(err);
})
.on('aborted', function() {
self.emit('aborted');
})
.on('data', function(buffer) {
self.write(buffer);
})
.on('end', function() {
if (self.error) {
return;
}
var err = self._parser.end();
if (err) {
self._error(err);
}
});
if (cb) {
var fields = {}, files = {};
this
.on('field', function(name, value) {
fields[name] = value;
})
.on('file', function(name, file) {
files[name] = file;
})
.on('error', function(err) {
cb(err, fields, files);
})
.on('end', function() {
cb(null, fields, files);
});
}
return this;
};

在parse中,将head信息解析出来这段是阻塞的。但是真正上传文件却是在req.on(data)中,是利用了事件驱动,是非阻塞的。也就是说,他的非阻塞模型依赖整个nodeJS事件分派架构。
那么像sleep那样消耗大量计算,但是又不能依赖nodeJS分派架构的时候怎么办?
现在介绍一种,类似于html5 WebWorker的方法。
将requestHandler.js改造如下:


代码如下:

var childProcess = require("child_process");
exports.hello = function(res) {
var n = childProcess.fork(__dirname + "/subProcess.js");
n.on('message', function() {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("say hello.");
res.end();
});
n.send({});
};
exports.upload = function(res) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("upload");
res.end();
};

并加入subProcess.js


代码如下:

function sleep(milliSecond) {
var startTime = new Date().getTime();
console.log(startTime);
while(new Date().getTime() <= milliSecond + startTime) {
}
console.log(new Date().getTime());
}
process.on('message', function() {
sleep(20000);
process.send({});
});

测试,当hello还在等待时,upload已经返回。
结语:
大概在最近,我看了博客园上的很多NodeJs文章,大家都认为NodeJS是异步的。但是是何种程度的异步,这个概念就没有几篇文章讲对了。
其实NodeJS,他是一个双层的架构。C++,和javascript。并且是单线程的。这点尤其重要。Node其实是C++利用v8调用js命令,为了实现调用顺序维护了一个Event序列。因此,在一个js function内部,他的调用绝对会对其他的function产生阻塞。所以,网上所说的process.nextTick和setTimeout等,都不能够产生新的线程,以保证不被阻塞。他所实现的,不过是Event序列的元素顺序问题。 相对于setTimeout,process.nextTick的实现要简单的多,直接加入Event序列的最顶层(有个啥啥事件)。而setTimeout是增加了一个c++线程,在指定的时间将callback加入Event序列
以Node的file io为例。他的readFile等函数,第二个参数是一个callback。那么node中第一件事就是记录下callback,然后调用底层c++,调用c++开始的过程,你可以看成是异步的。因为那已经到了c++,而不是js这块。所以,exec到callback为止是异步的,http.createServer到触发callback为止是异步的。还有很多,比如mysql的调用方法,不知道大家有没有看过源码,他就是socket发送命令,相信这个过程速度非常快。然后等待回调的过程Node用c++隐藏了,他也是异步的。
而我这篇文章想说明的是,如果再js端有花费大量时间的运算怎么办。就用我上面所说的方法,用js打开c++的线程,这个subprocess.js,不在node的event序列内部维护。是新的线程,因此不会阻塞其他的js function

(0)

相关推荐

  • 轻松创建nodejs服务器(9):实现非阻塞操作

    我们要将response对象(从服务器的回调函数onRequest()获取)通过请求路由传递给请求处理程序.随后,处理程序就可以采用该对象上的函数来对请求作出响应. 我们先对server.js做出修改: 复制代码 代码如下: var http = require("http"); var url = require("url"); function start(route, handle) {   function onRequest(request, respon

  • 轻松创建nodejs服务器(8):非阻塞是如何实现的

    这节我们来了解一下nodejs实现非阻塞操作的方法. 我们先来修改一下 start的处理程序: 复制代码 代码如下: var exec = require("child_process").exec; function start() {   console.log("Request handler 'start' was called.");   var content = "empty";   exec("ls -lah"

  • Nodejs进程管理模块forever详解

    接下来,就让我们看看forever能不能实现目标. 一.forever介绍 forever是一个简单的命令式nodejs的守护进程,能够启动,停止,重启App应用.forever完全基于命令行操作,在forever进程之下,创建node的子进程,通过monitor监控node子进程的运行情况,一旦文件更新,或者进程挂掉,forever会自动重启node服务器,确保应用正常运行. 二. forever安装 全局安装forever 复制代码 代码如下: ~ D:\workspace\javascri

  • 轻松创建nodejs服务器(7):阻塞操作的实现

    我们来看一下什么是阻塞操作: 我模拟一个sleep()方法来使hello star延迟10秒打印. requestHandlers.js 复制代码 代码如下: function start() {   console.log("Request handler 'start' was called.");   function sleep(milliSeconds) {  var startTime = new Date().getTime();  while (new Date().g

  • nodejs中实现阻塞实例

    node.js中与生俱来的单线程编程.回调函数异步式风格让我们有时喜有时忧.先说单线程,很多人会费解于node.js的单线程如何能做到高并发?这个问题不是本文重点,点到为止.澄清一点,node.js的单线程仅仅指javascript引擎是单线程的,无论如何我们没有办法在javascript中实现多线程和阻塞(本文用到的方法同样不是通过V8引擎实现同步的):但对于node.js的其他方面不代表不能多线程,例如IO.如果现在node.js遭受大量请求,而这些请求都是IO密集型的,那么此时node每接

  • 防止Node.js中错误导致进程阻塞的办法

    前言 在Node.js中,当某个回调函数发生了错误,整个进程都会崩溃,影响后面的代码执行. Node.js这样处理,是因为在发生未被捕获的错误时,进程的状态就不确定.之后也就无法正常工作了.如果错误始终不处理的话,就回一直抛出意料之外的错误,这样不利于调试. 防止错误导致的进程阻塞的方法主要有如下两种: 一. try-catch try-catch允许进行异常捕获,并让代码继续执行下去: 例如: 当函数抛出错误时,代码就停止执行了: (function() { var a = 0; a(); c

  • 利用NodeJS的子进程(child_process)调用系统命令的方法分享

    NodeJS子进程简介 NodeJS子进程提供了与系统交互的重要接口,其主要API有: 标准输入.标准输出及标准错误输出的接口. NodeJS子进程简介 NodeJS 子进程提供了与系统交互的重要接口,其主要 API 有: 标准输入.标准输出及标准错误输出的接口 child.stdin 获取标准输入 child.stdout 获取标准输出 child.stderr 获取标准错误输出 获取子进程的PID:child.pid 提供生成子进程的重要方法:child_process.spawn(cmd,

  • Nodejs极简入门教程(三):进程

    Node 虽然自身存在多个线程,但是运行在 v8 上的 JavaScript 是单线程的.Node 的 child_process 模块用于创建子进程,我们可以通过子进程充分利用 CPU.范例: 复制代码 代码如下: var fork = require('child_process').fork; // 获取当前机器的 CPU 数量 var cpus = require('os').cpus(); for (var i = 0; i < cpus.length; i++) {     // 生

  • NodeJs中的非阻塞方法介绍

    首先我们利用NodeJs先构建一个基本的服务器. index.js 复制代码 代码如下: var requestHandler = require("./requestHandler"); var server = require("./server"); var route = { "/hello": requestHandler.hello, "/upload": requestHandler.upload }; serv

  • 处理java异步事件的阻塞和非阻塞方法分析

    前言 由于多核系统普遍存在,并发性编程的应用无疑比以往任何时候都要广泛.但并发性很难正确实现,用户需要借助新工具来使用它.很多基于 JVM 的语言都属于这类开发工具,Scala 在这一领域尤为活跃.本系列文章将介绍一些针对 Java 和 Scala 语言的较新的并发性编程方法. 在任何并发性应用程序中,异步事件处理都至关重要.事件来源可能是不同的计算任务.I/O 操作或与外部系统的交互.无论来源是什么,应用程序代码都必须跟踪事件,协调为响应事件而采取的操作. Java 应用程序可采用两种基本的异

  • JavaScript中setter和getter方法介绍

    javascript中的setter.getter是平时接触比较少的方法,其本身也并不是标准方法,只在非ie浏览器里支持(ie内核也许有其他方法可以做到呢?暂时不知其解),但是加以利用可以做许多事情,比如: 1.对数据的访问限制: a.value是对value变量的getter方法调用,如果在getter方法实现中抛出异常,可以阻止对value变量的访问 2.对dom变量进行监听: window.name是一个跨域非常好用的dom属性(大名鼎鼎,详见百度),如果覆盖window.name的set

  • Spring配置中transactionAttributes的使用方法介绍

    最近碰到这个问题,在使用spring提供的JpaTemplate进行查询时,如果数据量超过100 条,查询效率就会明显降低.由于开始时使用JPA内部的双向关联,造成各实体内部关联过多,从而影响所有的操作,因此怀疑是因为JPA的关联关系所致.但是去掉关联关系后的效果不显著. 查找spring的相关配置,发现原来关于"transactionAttributes"有问题.原来的配置如下: <bean id="baseTransactionProxy" class=&

  • Java 8 中 Function 接口使用方法介绍

    目录 Java 8 中 Function 接口的介绍 Function 接口的用法 Function 接口的实例 Java 8 中 Function 接口的介绍 Java 8 中提供了一个函数式接口 Function,这个接口表示对一个参数做一些操作然后返回操作之后的值.这个接口的有一个抽象方法 apply,这个方法就是表明对参数做的操作. // Java Function 接口的定义 @FunctionalInterface public interface Function<T, R> {

  • JAVA中 终止线程的方法介绍

    在Java的多线程编程中,java.lang.Thread类型包含了一些列的方法start(), stop(), stop(Throwable) and suspend(), destroy() and resume().通过这些方法,我们可以对线程进行方便的操作,但是这些方法中,只有start()方法得到了保留. 在Sun公司的一篇文章<Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? >中详细讲解了舍弃这些方

  • Node中对非阻塞I/O、事件循环的知识点总结

    Node.js的主要特点 单线程.非阻塞I/O.事件驱动,这三个特点是相辅相成的. Node为了在低硬件服务器条件下高并发,所以减少了内存消耗,选择单线程: 因为只有一个线程,所以必须非阻塞I/O,每件事情都有回调函数: 为了合理调度,Node使用了事件环的机制,采用事件驱动来调度事件. 非阻塞I/O I/O是输入(input).输出(output)的简称. 阻塞I/O和非阻塞I/O的区别在于系统在输入与输出的期间,能不能接收输入. 举个例子:餐厅服务员招待客人 阻塞I/O:餐厅有多个服务员(多

  • JavaScript中Math对象的方法介绍

    1.比较最值方法 比较最值有两种方法,max() 和 min() 方法. 1.1 max() 方法,比较一组数值中的最大值,返回最大值. var maxnum = Math.max(12,6,43,58,70); alert(maxnum); //"70" 1.2 min() 方法,比较一组数值中的最小值,返回最小值. var minnum = Math.min(12,6,43,58,70); alert(minnum); //"6" 2.数值舍入方法 2.1 ce

  • JS中令人发指的valueOf方法介绍

    彭老湿近期月报里提到了valueOf方法,兴致来了翻了下ECMA5里关于valueOf方法的介绍,如下: 15.2.4.4 Object.prototype.valueOf ( ) When the valueOf method is called, the following steps are taken: 1. Let O be the result of calling ToObject passing the this value as the argument. 2. If O is

  • Ruby中操作文件的方法介绍

    Ruby提供了一套完整的I/O相关的内核模块中实现方法.所有I/O方法来自IO类. 类IO提供了所有的基本方法,如 read, write, gets, puts, readline, getc 和 printf. 本章将涵盖所有可供在Ruby中使用的基本I/O功能.如需使用更多的功能,请参考Ruby的IO类. puts 语句: 在前面的章节中,你指定值的变量和然后使用声明 puts 输出. puts 把语句指示程序显示存储在变量值.这将添加一个新行,每行末尾写出(输出). 例子: #!/usr

随机推荐