node的进程与线程
Node.js的单线程指的是主线程是“单线程”
1、每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。
2、主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
3、主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
4、主线程不断重复上面的第三步。
总结:node 的I/O操作非真实的阻塞型I/O而是将I/O扔进线程池然后等有事件执行完毕后,执行回调,线程归还给线程池。
node事件循环实现
Libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API。
libuv中的高效队列是用c语言, 只使用宏定义封装而成,
Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,事件循环机制也是它里面的实现。
int uv_run(uv_loop_t* loop, uv_run_mode mode) { int timeout; int r; int ran_pending;//首先检查我们的loop还是否活着//活着的意思代表loop中是否有异步任务//如果没有直接就结束
r = uv__loop_alive(loop); if (!r)
uv__update_time(loop);//传说中的事件循环,你没看错了啊!就是一个大while
while (r != 0 && loop->stop_flag == 0) { //更新事件阶段
uv__update_time(loop); //处理timer回调
uv__run_timers(loop); //处理异步任务回调
ran_pending = uv__run_pending(loop);//没什么用的阶段
uv__run_idle(loop);
uv__run_prepare(loop); //这里值得注意了
//从这里到后面的uv__io_poll都是非常的不好懂的
//先记住timeout是一个时间
//uv_backend_timeout计算完毕后,传递给uv__io_poll
//如果timeout = 0,则uv__io_poll会直接跳过
timeout = 0; if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
uv__io_poll(loop, timeout); //就是跑setImmediate
uv__run_check(loop); //关闭文件描述符等操作
uv__run_closing_handles(loop); if (mode == UV_RUN_ONCE) { /* UV_RUN_ONCE implies forward progress: at least one callback must have
* been invoked when it returns. uv__io_poll() can return without doing
* I/O (meaning: no callbacks) when its timeout expires - which means we
* have pending timers that satisfy the forward progress constraint.
*
* UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
* the check.
*/
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break;
} /* The if statement lets gcc compile it to a conditional store. Avoids
* dirtying a cache line.
*/
if (loop->stop_flag != 0) loop->stop_flag = 0; return r;
}
事件循环就是一个大while而已
小结:
1、Nodejs与操作系统交互,我们在 Javascript中调用的方法,最终都会通过 process.binding 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。
2、nodejs所谓的单线程,只是主线程是单线程,所有的网络请求或者异步任务都交给了内部的线程池去实现,本身只负责不断的往返调度,由事件循环不断驱动事件执行。
3、Nodejs之所以单线程可以处理高并发的原因,得益于libuv层的事件循环机制,和底层线程池实现。
4、Event loop就是主线程从主线程的事件队列里面不停循环的读取事件,驱动了所有的异步回调函数的执行,Event loop总共7个阶段,每个阶段都有一个任务队列,当所有阶段被顺序执行一次后,event loop 完成了一个 tick。
死锁
定义:死锁就是两个线程同时占用两个资源,但又在彼此等待对方释放锁。
同源任务
任务执行
1、任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
2、macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
3、micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
4、setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
5、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。
6、事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
7、其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
Browser端严格遵循第六条,执行完一个宏任务就会去检查微任务队列是否有需要执行的微任务,即使微任务内嵌套微任务,也会将嵌套的微任务执行完毕后(这点上nodejs与browser是相同的,对应的就是清空微任务的队列),再去宏任务队列执行下一个宏任务;
nodejs端则会将同源的任务放在一起执行,如果涉及到同源宏任务的嵌套,仍会将同源任务放在一起,但是内部的任务会放在下一次事件循环时执行。
代码验证:
console.log(1);
setTimeout(() => {
console.log(2)
new Promise((resolve) => {
console.log(6);
resolve(7);
}).then((num) => {
console.log(num);
})
});
setTimeout(() => {
console.log(3);
new Promise((resolve) => {
console.log(9);
resolve(10);
}).then((num) => {
console.log(num);
})
setTimeout(()=>{
console.log(8);
})
})
new Promise((resolve) => {
console.log(4);
resolve(5)
}).then((num) => {
console.log(num);
new Promise((resolve)=>{
console.log(11);
resolve(12);
}).then((num)=>{
console.log(num);
})
})
node 11以前
node 14
node 的 child-process
进程
定义:进程是计算机分配资源调度的基本单元,每一个应用程序启动会开启一个进程。分配固定的资源以及一个进程ID并且进程是独立的,进程之间可以通过IPC通信
线程
定义:线程是计算机计算的最小单元,进程可以有多个线程,但线程只属于一个进程。线程可共享进程的部分资源,node是单线程应用(一个进程一个线程)但node通过创建多进程来进行实现多线程。(以提高CPU利用率)