Node为JavaScript引入了一个复杂的概念,这在浏览器端从未有过:共享状态的并发。
通俗讲,Node中,你需要对回调函数如何修改当前内存中的变量(状态)特别小心。
阻塞和非阻塞
//PHP
print('Hello');
sleep(5);
print('sweatheart');
阻塞
//node
console.log('Hello');
setTimeout(function(){
console.log('Sweatheart');
},5000);
非阻塞
console.log('Hello');
setTimeout(function(){
console.log('Sweatheart');
},5000);
console.log('it's me');
如果再加入console.log语句,该语句会马上执行,先输出Hello,再输出it's me,过5s后输出sweatheart。
因为Node.js使用了事件轮询。从本质上来说,Node会先注册事件,随后不停地询问内核这些事件是否已经分发(触发)。当事件分发时(在例子中是时间过去5s),对应的回调函数就会被触发(console.log('Sweatheart');),然后继续执行下去。如果没有事件触发,则继续执行其他代码,直到有新事件时,再去执行对应的回调函数。
Node并发实现也采用了事件轮询。与timeout所采用技术一样,所有像http、net这样的原生模块中的IO部分也都采用了事件轮询技术。
Node是单线程的
var start = Date.now();
setTimeout(function(){
console.log(Data.now()-start);
for(var i = 0;i< 10000000000000000000;i++){}
},1000);
setTimeout(function(){
console.log(Data.now()-start);
},2000);
//1000
//>3000
单线程如何做到高并发 ——v8搭配非阻塞IO
v8执行JavaScript速度非常快,非阻塞IO确保了单线程执行时,不会因为有数据库访问或者硬盘访问等操作而导致被挂起。
单线程的缺点:
1.无法利用多核CPU
2.错误会引起整个应用退出,应用的健壮性值得考验。
3.大量计算占用CPU导致无法继续调用异步I/O。
深入浅出Node.js
- 异步IO
2.事件与回调函数
3.单线程
4.跨平台
Node的应用场景
I/O密集型
I/O密集的优势主要在于Node利用事件循环的处理能力,而不是启动每一个线程为每一个请求服务,资源占用极少。
单线程同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用。多线程编程模型也因为编程中的死锁、状态同步等问题让开发人员头疼。
Node在两者之间给出了它的方案:利用单线程,远离多线程死锁、状态同步等问题;利用异步I/O,让单线程程远离阻塞,以更好地使用CPU。
事件循环
在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。
观察者
在每个Tick的过程中,如何判断是否有事件需要处理呢?每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。
事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求等则是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到响应的观察者那里,事件循环则从观察者那里取出事件并处理。
整个异步I/O的流程

从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用。
创建包含JavaScript层传入的参数和当前方法以及回调函数的请求对象放入线程池后,JavaScript调用立即返回,由JavaScript层面发起的异步调用的第一阶段就此结束。JavaScript线程可以继续执行当前任务的后续操作。当前的I/O操作在线程池中等待执行,不管它是否阻塞I/O,都不会影响到JavaScript线程的后续执行,如此就达到了异步的目的。
线程池中的I/O操作调用完毕之后,将线程归还线程池,并将请求对象加入到I/O观察者的队列中。最后I/O观察者执行请求对象的回调函数。
事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。
事实上,在Node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。
另一个需要重视的观点则是,除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)则是可以并行起来的。
js单线程,I/O操作线程池,回调函数-事件循环(队列)

网络套接字上侦听到的请求都会形成事件交给I/O观察者。事件循环会不停地处理这些网络I/O事件。如果JavaScript有传入回调函数,这些事件将会最终传递到业务逻辑层进行处理。
利用事件循环的方式,JavaScript线程像一个分配任务和处理结果的大管家,I/O线程池里的各个I/O线程都是小二,负责兢兢业业地完成分配来的任务,小二与管家之间互不依赖。
网友:
Node.Js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)
下面这个过程:
1,你用浏览器访问nodejs服务器上的"/about.html"
2,nodejs服务器接收到你的请求,调用一个函数从磁盘上读取这个文件。
3,这段时间,nodejs webserver在服务后续的web请求。
4,当文件读取完毕,有一个回调函数被插入到nodejs的服务队列尾中。
5,队列前都执行完了,nodejs webserver运行这个函数,实际上就是渲染(render)了about.html页面返回给你的浏览器。
就节省了几微秒时间,但对于需要相应大量用户的web server很重要。
菜鸟教程
Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
模块
一个正常的JavaScript文件会被包装成如下的样子:
(function (exports, require, module, __filename, __dirname) {
var math = require('math');
exports.area = function(radius) {
return Math.PI * radius * radius;
};
});
这就是exports, require, module, __filename, __dirname没有声明而能使用的原因。
异常处理
Node在处理异常上形成了一种约定, 将异常作为回调函数的第一个实参传回,如果为空值,表明异步调用没有异常抛出。
fs.open(url, function(err, result));
var fs.open = function(url, callback) {
// ...尝试打开url,如果出错抛出error
if(error) {
return callback(error);
}
callback(null, results);
}
Node为JavaScript引入了一个复杂的概念,这在浏览器端从未有过:共享状态的并发。
通俗讲,Node中,你需要对回调函数如何修改当前内存中的变量(状态)特别小心。
阻塞和非阻塞
阻塞
非阻塞
如果再加入console.log语句,该语句会马上执行,先输出Hello,再输出it's me,过5s后输出sweatheart。
因为Node.js使用了事件轮询。从本质上来说,Node会先注册事件,随后不停地询问内核这些事件是否已经分发(触发)。当事件分发时(在例子中是时间过去5s),对应的回调函数就会被触发(console.log('Sweatheart');),然后继续执行下去。如果没有事件触发,则继续执行其他代码,直到有新事件时,再去执行对应的回调函数。
Node并发实现也采用了事件轮询。与timeout所采用技术一样,所有像http、net这样的原生模块中的IO部分也都采用了事件轮询技术。
Node是单线程的
单线程如何做到高并发 ——v8搭配非阻塞IO
v8执行JavaScript速度非常快,非阻塞IO确保了单线程执行时,不会因为有数据库访问或者硬盘访问等操作而导致被挂起。
单线程的缺点:
1.无法利用多核CPU
2.错误会引起整个应用退出,应用的健壮性值得考验。
3.大量计算占用CPU导致无法继续调用异步I/O。
深入浅出Node.js
2.事件与回调函数
3.单线程
4.跨平台
Node的应用场景
I/O密集型
I/O密集的优势主要在于Node利用事件循环的处理能力,而不是启动每一个线程为每一个请求服务,资源占用极少。
单线程同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用。多线程编程模型也因为编程中的死锁、状态同步等问题让开发人员头疼。
Node在两者之间给出了它的方案:利用单线程,远离多线程死锁、状态同步等问题;利用异步I/O,让单线程程远离阻塞,以更好地使用CPU。
事件循环
在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。
观察者
在每个Tick的过程中,如何判断是否有事件需要处理呢?每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。
事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求等则是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到响应的观察者那里,事件循环则从观察者那里取出事件并处理。
整个异步I/O的流程

从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用。
创建包含JavaScript层传入的参数和当前方法以及回调函数的请求对象放入线程池后,JavaScript调用立即返回,由JavaScript层面发起的异步调用的第一阶段就此结束。JavaScript线程可以继续执行当前任务的后续操作。当前的I/O操作在线程池中等待执行,不管它是否阻塞I/O,都不会影响到JavaScript线程的后续执行,如此就达到了异步的目的。
线程池中的I/O操作调用完毕之后,将线程归还线程池,并将请求对象加入到I/O观察者的队列中。最后I/O观察者执行请求对象的回调函数。
事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。

事实上,在Node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。
另一个需要重视的观点则是,除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)则是可以并行起来的。
js单线程,I/O操作线程池,回调函数-事件循环(队列)
网络套接字上侦听到的请求都会形成事件交给I/O观察者。事件循环会不停地处理这些网络I/O事件。如果JavaScript有传入回调函数,这些事件将会最终传递到业务逻辑层进行处理。
利用事件循环的方式,JavaScript线程像一个分配任务和处理结果的大管家,I/O线程池里的各个I/O线程都是小二,负责兢兢业业地完成分配来的任务,小二与管家之间互不依赖。
网友:
Node.Js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)
下面这个过程:
1,你用浏览器访问nodejs服务器上的"/about.html"
2,nodejs服务器接收到你的请求,调用一个函数从磁盘上读取这个文件。
3,这段时间,nodejs webserver在服务后续的web请求。
4,当文件读取完毕,有一个回调函数被插入到nodejs的服务队列尾中。
5,队列前都执行完了,nodejs webserver运行这个函数,实际上就是渲染(render)了about.html页面返回给你的浏览器。
就节省了几微秒时间,但对于需要相应大量用户的web server很重要。
菜鸟教程
Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
模块
一个正常的JavaScript文件会被包装成如下的样子:
这就是exports, require, module, __filename, __dirname没有声明而能使用的原因。
异常处理
Node在处理异常上形成了一种约定, 将异常作为回调函数的第一个实参传回,如果为空值,表明异步调用没有异常抛出。