Node.js事件驱动的异步模型(Node.js自学第七天)

356 阅读5分钟

进程

进程:Process/Task,是程序的动态执行。操作系统将可执行文件从磁盘调入内存,为其分配必需的可执行代码内存和数据空间,这一过程就创建了一个进程——进程是操作系统分配内存的基础单位。

image.png

线程

线程:Thread,是进程内部执行代码的基础单位。一个进程内可以同时创建多个线程,并发的执行多端代码。

每个线程需要自己独立的数据内存空间,大小为2MB;所以,一台具有8GB内存的计算机,理论上可以同时存在4000个线程。

进程和线程的关系

进程是操作系统创建任务分配内存的基本单位;
线程是进程内部执行代码的基本单位;
线程处于进程内部。

一个进程内必须至少存在一个线程;
一个进程内也可以同时存在多个线程,这些线程间并发执行(线程切换计算速度快,宏观上是在并发执行,微观上的真正的并发执行取决于计算机的处理器核数)。

未命名文件 (2).png

单线程服务器

早期的"单线程服务器"中,服务器端只有一个线程,依次为客户端请求提供服务。某一个时刻只能为一个客户端提供响应。

特点:
程序设计简单;
运行效率太低。

多线程服务器

为了同时处理多个客户端请求,很多服务器应用都涉及为多线程运行模式,即每个接收到一个个客户端连接请求,都会有创建一个专门的服务线程。

未命名文件 (4).png

多线程模型的特点

多线程模型作为广泛采用的服务器运行模型,被大量的服务器程序所采用(如Apatch Httpd)。对于CPU密集型请求,可以充分利用多核CPU优势,同时为尽可能多的客户端提供服务。

但是,多线程模型也存在下列问题:
(1)每个线程内部的操作都是线性执行的,耗时操作会阻塞后续操作;
(2)受限于线程总数的限制,无法并发的处理大量的请求;
(3)过多过频繁的线程上下文切换,产生了更多的CPU开销;
(4)多线程并发往往伴随着互斥和死锁等问题,增加了程序设计复杂度。

Node.js中的单线程服务器

为了解决多线程服务器在高并发的I/O密集型应用中的不足,同时避免早期简单单线程服务器的性能障碍,Node.js采用了基于"事件循环"的非阻塞式单线程模型,实现了如下两个目标:
(1)保证每个请求都可以快速响应;
(2)实现远超过多线程模型的并发连接数。

提示:Node.js在JS层面是单线程的——没有创建新线程的机制。但是在底层的C/C++层面是多线程的,即访问底层操作系统服务时,存在多个并发工作线程的情形——使用了线程池。

阻塞执行

阻塞(Block):也称为同步执行(Synchroize),只有前面的操作全部执行完成,才能开始后续操作。(常规的多线程服务器内部都是采用的是阻塞执行)

var conn=mysql.createConnection(...);		//步骤1
var result=conn,query('INSERT...');		  //步骤2
conn.end();															//步骤3

非阻塞执行

非阻塞(Non—block):也称为异步执行(Asynchronize),后面的操作不必等待之前操作的执行完毕,可以先执行。

const fs=require('fs');
console.log('读取请求数据');							//操作1
fs.writeFile('app.log','访问日志',()=>{   //操作2
	console.log('写出操作日志');
})
console.log('输出响应数据');							//操作3
//读取请求数据
//输出响应数据
//写出操作日志

异步回调

Node.js中的业务代码,都是在单一的主线程中执行的;当遇到耗时的阻塞操作时(如文件IO、网络访问、数据库请求等),不会等待其执行完毕,而是注册一个处理函数执行结果的回调函数,继续执行后续的代码。

待耗时的阻塞操作执行完成时,其对应的回调函数会转入回调函数队列,主线程在下次事件循环时会执行这些回调函数。

同步函数调用:

const fs=require('fs');
var data=fs.readFileSync('app.log');
console.log('文件内容:',data);
console.log('程序执行完成!');
//文件内容:121212
//程序执行完成!

异步函数调用:

const fs=require('fs');
fs.readFile('app.log',function(err,data){
	console.log('文件内容:',data)
})
console.log('程序执行完成!');
//程序执行完成!
//文件内容:121212

事件驱动

未命名文件 (7).png

未命名文件 (10).png

事件驱动编程

同步编程模型:

var conn=openConnection();
var data=conn.readDate();
response.writeData(data);

事件驱动的异步编程模型:

requset.on('getData',()=>{
	var conn=openCionnction();
  conn.on('open',()=>{
  	var data=readDate();
    data.on('complete',()=>{
    	response.sendData();
      return;
    })
  })
})

事件循环

未命名文件 (11).png

注意:虽然对异步操作很擅长,它可以用多次事件回调的形式来处理。但是一次事件回调里,需要大量CPU操作,他会阻塞后续所有的其他操作,其他的事件回调都不会执行。

setImmediate(()=>{
    console.log('immdiate func...')
});
while(true){

}
console.log('脚本执行完成...')
//一直处于等待,其他事件回调不会执行,也不会有其他输出