Node学习常见概念理解

294 阅读15分钟

进程和线程

  • 进程包含线程
  • 一个进程只有一个主线程 可以有多个子进程
  • node可以开子进程 (child_process)

进程

  • 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例,程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
  • Linux系统函数fork()可以在父进程中创建一个子进程,这样的话,在一个进程接到来自客户端新的请求时就可以复制出一个子进程让其来处理,父进程只需负责监控请求的到来,然后创建子进程让其去处理,这样就能做到并发处理。

线程

  • 线程是程序执行时的最小单位,它是进程进程的一个执行流,是CPU调度和分配的最小单位,一个进程可以由多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆、栈和局部变量。线程有CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

区别

  • 进程是资源分配最小单位,线程是程序执行的最小单位。
  • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵;而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
  • 线程之间的通信更方便,同一进程下的线程共享本进程的地址空间、共享全局变量、静态变量等数据;而进程之间的资源是独立的,进程之间的通信需要以通信的方式(IPC--进程间通信)进行。不过如何处理好同步与互斥是编写多线程程序的难点--多线程的同步与互斥。

为什么js是单线程的?

  • JavaScript最初被设计为浏览器的脚本语言,如果被设计多线程的,那么当两个线程同时向同一个DOM下达相互矛盾的命令,浏览器究竟该如何执行呢?到底是删除还是添加?所以。。。

JS为什么需要异步?

  • 如果js不存在异步,所有代码从上往下执行,如果上一行代码解析很长,那么下面的代码就会被阻塞,对于用户而言,阻塞意味着“卡死”,这样就导致了很差的用户体验。所以。。。

js任务队列

  • js中,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
  • 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  • 异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

js异步执行的运行机制:

  • 1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  • 2、主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件
  • 3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 4、主线程不断重复上面的第三步。

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

异步 同步 阻塞 非阻塞

  • 同步:在调用操作未完成前,调用者一直在等待这个结果,不得到结果不返回
  • 异步:在调用后,调用者直接返回,不主动获取和等待调用结果。而是被调用者通过通知或者回调函数来通知调用者。
  • 阻塞:调用时,由于被调用者状态未就绪,导致调用线程被挂起。状态未就绪并不是指调用者运行缓慢,时间久。
  • 非阻塞:调用时,被调用者如果就绪则立即返回结果,如果未就绪也会返回一个错误值,告诉调用者当前的状态。调用者可根据错误值选择再次调用,还是执行异常处理。
  • 同步异步:被调用方取决的状态-同步异步侧重于描述调用者进行调用之后的行为 如:被调用者同步执行
  • 指的是调用方的状态-阻塞和非阻塞侧重于描述被调用者在执行时所处于的状态 如:调用被阻塞

JS单线程又是如何实现异步的呢?

  • 是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制
  • 可以理解为:浏览器为js的异步任务单独开了一个线程,将异步事件放入Event Queue,待异步任务完成后,通过回调函数通知主线程调用Event Queue中的任务,而js一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。所以,js是一直是单线程的,浏览器才是实现异步的那个家伙。

事件环 Event Loop (浏览器的事件环 node事件环)

浏览器事件环

  • 宏任务:包括整体代码的script,setTimeout,setInterval I/O操作 onClick事件
  • 微任务:Promise.then
  • 事件环(Event Loop):同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。
  • js执行机制(见图解):
    • 1、当一个任务进入执行栈,同步和异步任务分别进入不同的执行"场所",同步的进入主线程,
    • 2、异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。
    • 3、不同类型的任务会进入对应的Event Queue,可以理解为:宏任务进入宏任务的Queue,微任务进入微任务的Queue。
    • 4、事件循环的顺序,决定js代码的执行顺序;每当执行一个宏任务时,会先清空Event Queue中的微任务。
    • 5、所以,当主线程内的任务执行完毕为空时,会去Event Queue读取对应的函数,进入主线程执行,Event Queue分为宏任务队列和微任务队列。
    • 6、因为4和5,此时会先去微任务的队列读取对应的函数,并进入主线程执行,
    • 7、待该微任务Queue清空后,再去宏任务的Queue中读取对应的函数,并放入主线程执行;
    • 8、但是,每当执行完一个宏任务后,会再去清空微任务的Queue
    • 9、上述过程会不断重复,也就是常说的Event Loop(事件循环)。
  • 注意:
    • 执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。
    • javascript是一门单线程语言,Event Loop是javascript的执行机制

浏览器模型

  • 浏览器的user Interface (用户界面)、Browser engine(浏览器引擎)、Rendering engine(渲染引擎)、Data Persistence(数据存储)都是一个进程
  • Rendering engine(渲染引擎)下有三个线程:Net working(网络请求)、JavaScript Interpreter(js解释器)、UI线程 注意:UI线程和JS是互斥的,共用一个线程
  • 一般来说浏览器会有以下几个线程:
    • 1、js引擎线程 (解释执行js代码、用户输入、网络请求)
    • 2、GUI线程 (绘制用户界面、与js主线程是互斥的)
    • 3、http网络请求线程 (处理用户的get、post等请求,等返回结果后将回调函数推入任务队列)
    • 4、定时触发器线程 (setTimeout、setInterval等待时间结束后把执行函数推入任务队列中)
    • 5、浏览器事件处理线程(将click、mouse等交互事件发生后将这些事件放入事件队列中)
  • 宏任务macrotasks: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • 微任务microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver

node事件环

  • Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。
  • 宏任务:setTimeout,setInterval setImmediate I/O操作 MessageChannel???
  • 微任务:Promise,process.nextTick MutationObserver
  • vue有个方法:nextTick 下一个队列--微任务或宏任务
  • Node.js的运行机制如下:
    • 1、V8引擎解析JavaScript脚本。
    • 2、解析后的代码,调用Node API。
    • 3、libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
    • 4、V8引擎再将结果返回给用户。
    • node事件环与浏览器事件环类似,当主线程内的任务执行完毕为空时,会去Event Queue读取对应的函数,并进入主线程执行
      • 多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。
  • Node.js的事件环与浏览器事件环差别:
    • 1、浏览器事件环:每执行一个宏任务时先清空当前微任务队列,再执行下一个宏任务--相同点
    • 2、node事件环:代码执行到宏任务时,会清空当前所有宏任务中内嵌的微任务,待所有微任务queue清空后,再执行宏任务
      • 不同点1:可以理解为V8给每个宏任务分配了一个线程,多个宏任务线程同时运行,几乎同时将内嵌的微任务暴露出来放到微任务queue,待把当前所有线程下所有暴露出来的所有微任务执行完毕,再执行宏任务
      • 不同点2:node事件环中,setImmediate(A)与setTimeout(B)B Loop触发,其回调函数执行顺序不确定。令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面
      • nodejs中每个回调函数均由主线程中的事件轮询来调用

队列 和 栈 堆

  • 堆 程序运行动态分配内存,,而不是在程序编译时
    • 堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。
    • 堆是应用程序在运行的时候请求操作系统分配给自己内存
    • 堆是指程序运行时申请的动态内存,而栈只是指程序编译时一种使用堆的方法(即先进后出)。
  • 栈 先进后出 模拟:unshift()shift()
    • 又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底--采用LIFO(last in first out)。
    • 栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来(先进后出-后进先出)
    • 栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有FIFO的特性,在编译的时候可以指定需要的Stack的大小。
  • 队列 先进先出 模拟:push()和shift()
    • 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
    • 队列中没有元素时,称为空队列,建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。
    • 队列采用的FIFO(first in first out),新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由结构体间接而成,遍历也方便。(先进先出)
  • 队列和栈的相同点:
    • 都是线性结构
    • 插入操作都是限定在表位进行
    • 都可以通过顺序结构和链式结构实现
  • 队列和栈的不同点:
    • 删除数据元素的位置不通,栈 pop()-删除操作在表尾进行;队列 shift()--删除操作在表头进行
    • 应用场景不通:

      栈-函数调用和递归、深度优先搜索遍历 ,表达式的转换和求值, 队列-计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等

    • ps:堆是指程序运行是申请的动态内存,而栈只是指一种使用堆的方法
  • 堆、栈、队列之间的区别
    • 堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。
    • 栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来。(后进先出)
    • 队列只能在队头做删除操作,在队尾做插入操作.而栈只能在栈顶做插入和删除操作。(先进先出)

node的应用场景

  • Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具
  • NodeJS的异步I/O原理:node事件环-->NodeJS处理并发的能力强,但处理计算和逻辑的能力反而很弱,因此,如果我们把复杂的逻辑运算都搬到前端(客户端)完成,而NodeJS只需要提供异步I/O,这样就可以实现对高并发的高性能处理。情况就很多啦,比如:RESTFUL API、实时聊天、客户端逻辑强大的单页APP,具体的例子比如说:本地化的在线音乐应用,本地化的在线搜索应用,本地化的在线APP等。
  • NodeJS它的所有I/O、网络通信等比较耗时的操作,都可以交给worker threads(也就是原生的 Nodejs 多线程支持API)执行再回调,所以很快。但CPU的正常操作,它就只能自己抗了。
  • Web服务器的瓶颈在于并发的用户量,对比Java和Php的实现方式
  • HTML5中的Web Worker:Web Worker是工作线程的意思(本文中如果没有特别说明,那么Web Worker是指的专有线程(Dedicated Worker),Web Worker、工作线程和专有线程在本文中是指代同样的东西),它是HTML5中新增加的概念
  • i/o 密集 web端高并发问题
  • I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密
  • Web主要场景就是接收客户端的请求读取静态资源和渲染界面,所以Node非常适合Web应用的开发
  • nodejs不适用与CPU密集型场景-单线程同步阻塞了
  • node只能处理同步阻塞和异步非阻塞

中间层 服务端渲染

  • 并发量
  • V8引擎

请求

  • tomcat为我们启服务是一种web服务器,负载均衡去处理高并发; 数据库: MySQL MongoDB Redis
  • 多线程同步阻塞 - Java一个线程处理一个请求,每个请求的线程是同步阻塞的,服务器要等待数据返回;比较耗性能,如若启动Tomcat配置线程数,那么高并发的时候就要等待了
  • 单线程异步非阻塞 - node
  • Java 同步多线程 node 异步单线程

js的组成 ECMAScript (BOM DOM)

  • node没有BOM DOM 只有ECMAScript

开发node 离不开大量的第三方(模块)

js解析器和 ui 线程 是共用同一个线程的

  • js 操作dom (多线程) js的必须是单线程 (变成模型更加简单)
  • Java 同步多线程--若同时操作一个文件--锁的问题