# 多进程优化:Node.js子进程与线程

Node遵循的是单线程单进程的模式,node的单线程是指js的引擎只有一个实例,且在nodejs的主线程中执行,同时node以事件驱动的方式处理IO等异步操作。node的单线程模式,只维持一个主线程,大大减少了线程间切换的开销。

但是node的单线程使得在主线程不能进行CPU密集型操作,否则会阻塞主线程。对于CPU密集型操作,在node中通过 child_process 可以创建独立的子进程,父子进程通过IPC通信,子进程可以是外部应用也可以是node子程序,子进程执行后可以将结果返回给父进程。

# 进程和线程基本概念

拿出在教科书里的概念:

  • 1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
  • 2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行;
  • 3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
  • 4、系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。进程和线程的关系:

一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;

资源分配给进程,同一进程的所有线程共享该进程的所有资源;

处理机分给线程,即真正在处理机上运行的是线程;

线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体。

基本总结,一个进程可以有多个线程,线程之间可以相互通信。

进程是 CPU资源分配 的最小单位;进程内的线程共享进程的资源。

线程是 CPU计算调度 的最小单位。线程是真正用来执行程序的,执行计算的。

# 图示解析

进程和线程

  • 进程好比图中的工厂,有单独的专属自己的工厂资源。
  • 线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  • 工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存。
  • 多个工厂之间独立存在。

# 多进程与多线程

  • 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
  • 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

# Node.js的运行机制

  • V8引擎解析JavaScript脚本。
  • 解析后的代码,调用Node API。
  • libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  • V8引擎再将结果返回给用户。

# Node.js 事件循环

Node.js 通常情况下是单进程的。

  • 主线程运行 V8 和 Javascript
  • 多个子线程通过 事件循环 被调度

可以抽象为:主线程对应于老板,正在工作。一旦发现有任务可以分配给职员(子线程)来做,将会把任务分配给底下的职员来做。同时,老板继续做自己的工作,等到职员(子线程)把任务做完,就会通过事件把结果回调给老板。老板又不停重复处理职员(子线程)子任务的完成情况。

老板(主线程)给职员(子线程)分配任务,当职员(子线程)把任务做完之后,通过事件把结果回调给老板。老板(主线程)处理回调结果,执行相应的 Javascript。

# Node.js子进程

Node是单进程,这就会存在一个问题,无法充分利用CPU等资源。Node提供 child_process 实现子进程,从而试下广义的多进程模式。通过 child_process 可以实现一个主进程,多个子进程模式,主进程称为 master 进程,子进程称为worker 进程。

每个子进程总是带有三个流对象:child.stdin, child.stdoutchild.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的流对象。

Node 提供了 child_process 模块来创建子进程,创建的方法包括 异步同步

# 异步的进程

  • child_process.exec:使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
  • child_process.execFile:函数类似于 child_process.exec(),但默认情况下不会衍生 shell。 指定的可执行文件 file 会被直接衍生作为新的进程,使其比 child_process.exec() 稍微更高效。
  • child_process.fork:是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个 IPC 通信通道,用于进程之间的通信。每个进程都有自己的内存,带有自己的 V8 实例。 由于需要额外的资源分配,因此不建议衍生大量的 Node.js 子进程。
  • child_process.spawn:使用指定的命令行参数创建新进程。

# 创建同步的进程

child_process.spawnSync()child_process.execSync()child_process.execFileSync() 方法是同步的,并且将会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到衍生的进程退出。

# 参考

更新时间: 6/30/2020, 12:24:10 AM