You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
//src/node_main_instance.h// ...// Start running the node.js instances, return the exit code when finished.intRun();// ...// src/node_main_instance.ccnamespacenode{// ...intnodeMainInstance::Run(){do{// 执行一次libuv事件循环uv_run(env->event_loop(),UV_RUN_DEFAULT);// 执行v8中的一些挂起的任务队列的函数per_process::v8_platform.DrainVMTasks(isolate_);// 检查事件循环是否还有待处理more=uv_loop_alive(env->event_loop());// 继续if(more&&!env->is_stopping())continue;// 无待处理if(!uv_loop_alive(env->event_loop())){// 检查process.on(beforeExit)事件,若无退出if(EmitProcessBeforeExit(env.get()).IsNothing())break;}// 若有继续下一轮循环处理一下// Emit `beforeExit` if the loop became alive either after emitting// event, or after running some callbacks.more=uv_loop_alive(env->event_loop());}while(more==true&&!env->is_stopping());//....}}
const{ Process }=internalBinding("process_wrap");this._handle=newProcess();ChildProcess.prototype.spawn=function(options){//..consterr=this._handle.spawn(options);//..};
// lib/net.jsServer.prototype.listen=function(...args){// ....// 关键逻辑if(typeofoptions.port==="number"||typeofoptions.port==="string"){validatePort(options.port,"options.port");backlog=options.backlog||backlogFromArgs;// start TCP server listening on host:portif(options.host){lookupAndListen(this,options.port|0,options.host,backlog,options.exclusive,flags);}else{// Undefined host, listens on unspecified address// Default addressType 4 will be used to search for master serverlistenInCluster(this,null,options.port|0,4,backlog,undefined,options.exclusive);}returnthis;}//....};
// lib/internal/cluster/round_robin_handle.jsRoundRobinHandle.prototype.distribute=function(err,handle){ArrayPrototypePush(this.handles,handle);const[workerEntry]=this.free;// this.free is a SafeMap// 选择一个空闲的进程处理if(ArrayIsArray(workerEntry)){const{0: workerId,1: worker}=workerEntry;this.free.delete(workerId);this.handoff(worker);}};RoundRobinHandle.prototype.handoff=function(worker){//...consthandle=ArrayPrototypeShift(this.handles);constmessage={act: "newconn",key: this.key};//...// 取出handler分发给子进程,消息的act为newconnsendHelper(worker.process,message,handle,(reply)=>{// 使用轮询进行分发if(reply.accepted)handle.close();elsethis.distribute(0,handle);// Worker is shutting down. Send to another.this.handoff(worker);});};
概念
进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单位,操作系统的其他所有内容都是围绕着进程展开的
线程是操作系统能够进行运算调度的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行
一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存,线程之间可以共享对象、资源
单线程
top -pid 28840
查看线程数可见在这种情况下有 7 个线程一个 node 进程通常包含:
process.env.UV_THREADPOOL_SIZE
进行设置。网络 I/O 不占用线程池)事件循环
既然 js 执行线程只有一个,那么 node 还能支持高并发在于 node 进程中通过 libuv 实现了一个事件循环机制,当执主程发生阻塞事件,如 I/O 操作时,主线程会将耗时的操作放入事件队列中,然后继续执行后续程序。
事件循环会尝试从 libuv 的线程池中取出一个空闲线程去执行队列中的操作,执行完毕获得结果后,通知主线程,主线程执行相关回调,并且将线程实例归还给线程池。通过此模式循环往复,来保证非阻塞 I/O,以及主线程的高效执行
整个流程分为 2 个 while 循环
uv_run
+DrainVMTasks
uv_run
事件循环主要有 libuv 提供的两个函数
uv_run
和uv_loop_alive
uv_run(env->event_loop(), UV_RUN_DEFAULT)
执行一轮事件循环 。UV_RUN_DEFAULT
是 libuv 执行事件循环的执行模式,事件循环会一直运行直到没有更多的事件要处理或者程序被强制退出uv_run
代码如下,它的返回值是是否有活跃事件
uv_backend_timeout
正常是查询最近的定时器间隔,有几种情况返回 0,即有一些更重要的事要做而不是同步等待 io 事件其中
idle_handles
由setImmediate
设置执行一些高优任务,马上进入下一次循环处理setImmediate
回调一次事件循环总结
uv_loop_alive(env->event_loop())
即上面提到的 uv__loop_alive, 判断有没有活跃的事件(事件监听 I/O、定时器等)
总结
严格意义上来说对开发者写代码来说是单线程的,但是对于底层来说是多线程(例如源码中会有 SafeMap 这种线程安全的 map)。由于对于开发者来说是单线程,所以在 Node.js 日程开发中通常不会存在线程竞争的问题和线程锁的一些概念
子进程
从上面的单线程机制可知 Node.js 使用事件循环机制来实现高并发的 I/O 操作。但是如果代码中遇到 CPU 密集型场景,主线程将会长时间阻塞,无法处理额外的请求。为了解决这个问题,并充分发挥多核 CPU 的性能,Node 提供了 child_process 模块用于创建子进程。通过将 CPU 密集型操作分配给子进程处理,主线程可以继续处理其他请求,从而提高性能
主要提供了 4 个方法
spawn(command[, args][, options])
:以指定的命令及参数数组创建一个子进程。可以通过流来处理子进程的输出和错误信息,大数据量exec(command[, options][, callback])
:对spawn()
函数的封装,可以直接传入命令行执行,并以回调函数的形式返回输出和错误信息execFile(file[, args][, options][, callback])
:类似于exec()
函数,但默认不会创建命令行环境(相应的无法使用一些 shell 的操作符),而是直接以传入的文件创建新的进程,性能略微优于exec()
。fork(modulePath[, args][, options])
:内部使用spawn()
实现 ,只能用于创建 node.js 程序的子进程,默认会建立父子进程之间的 IPC 信道来传递消息lib/child_process.js
lib/internal/child_process.js
src/node_binding.cc
src/process_wrap.cc
Cluster
基于
child_process
node 提供了专门用于创建多进程网络服务的[cluster](https://nodejs.org/api/cluster.html)
模块创建多个子进程,并在每个子进程中启动一个独立的 HTTP 服务器进行监听和处理客户端请求
如何解决多个工作进程监听一个端口的问题
NODE_UNIQUE_ID
,在创建子进程时传入http.createServer
->lib/_http_server.js#Server
-lib/_http_server.js#Server
继承于 TCP 的lib/net.js#Server
listen
方法调用的是lib/net.js#Server
lookupAndListen
内部其实也是对option.host
进行调dns
模块查询host
后调的listenInCluster
在 listenInCluster 函数中,会判断当前的进程是否是主进程,
_listen2
监听server
。_listen2
就是 cluster 出现之前的监听函数Server.prototype._listen2 = setupListenHandle; // legacy alias
handle
(const { TCP } = internalBinding('tcp_wrap');
,c++层暴露的用于处理 TCP 的对象),然后在主进程的 handle 上进行监听cluster._getServer
实现主要逻辑是向当前工作进程发送一个类型为 queryServer 的消息,这个消息会被处理成 cluster 内部消息后发送给主进程
主进程有相应的响应 queryServer 消息的地方
RoundRobinHandle 也会覆盖主进程的
Server.handle
的 onconnection 逻辑,将其替换成 round-robin 逻辑,即this.handle.onconnection = (err, handle) => this.distribute(err, handle);
再回到这个代码
在 rr 函数中创建一个 fake handler 返回
这个 handler 就是上面 rr 函数中获取的 handler,而
_listen2
内部调用的实际是 fake handler 中的 listen 空函数,实际上工作进程并没有对端口进行监听RoundRobinHandle 的
distribute
实现工作进程处理
newconn
消息总结
当主进程的 RoundRobinHandle 接收到一个监听请求时,它会调用
distribute
函数将客户端的 handle(socket 对象) 传递给工作进程。具体的逻辑为:将这个 handle 保存到队列中,并从工作进程队列中获取一个空闲的工作进程。如果存在空闲的工作进程,则从队列中取出一个工作进程并向其发送act: "newconn"
消息,以将 handle 传递给工作进程。工作进程会使用此 handle 与客户端建立连接,并向主进程发送一条消息表示是否接受了请求。主进程通过 accepted 属性来判断工作进程是否已经接受了请求。如果是则关闭与客户端的连接,并让其与工作进程进行通信。最后,主进程会不断地轮询上述过程以处理更多的客户端请求多线程
为了降低 js 对于 CPU 密集型任务计算的负担,node.js v10 之后引入了 worker_threads。可以在 nodejs 进程内可以创建多个线程。主线程和 worker 线程之间可以通过
parentPort
实现通信,worker 线程之间可以使用MessageChannel
进行通信。多个线程之间可以使用SharedArrayBuffer
实现共享内存,无需序列化多线程下共享内存为避免者竞态条件。node.js 也提供了
Atomics
对象用于执行原子操作,可以保证多个线程对共享内存的读写操作原子性The text was updated successfully, but these errors were encountered: