线程和进程是操作系统中的两个概念:
听起来很抽象,我们直观一点解释:
再用一个形象的例子解释:
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
你可以在Mac的活动监视器或者Windows的资源管理器中查看到很多进程:
JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。
但是JavaScript的代码执行是在一个单独的线程中执行的:
分析下面代码的执行过程:
const name = "coderwhy";
// 1.将该函数放入到调用栈中被执行
console.log(name);
// 2. 调用栈
function sum(num1, num2) {
return num1 + num2;
}
function bar() {
return sum(20, 30);
}
console.log(bar());
如果在执行JavaScript代码的过程中,有异步操作呢?
const name = "coderwhy";
// 1.将该函数放入到调用栈中被执行
console.log(name);
// 2.调用栈
function sum(num1, num2) {
return num1 + num2;
}
function bar() {
return sum(20, 30);
}
setTimeout(() => {
console.log("settimeout");
}, 1000);
const result = bar();
console.log(result);
那么,传入的一个函数(比如我们称之为timer函数),会在什么时候被执行呢?
但是事件循环中并非只维护着一个队列,事实上是有两个队列:
那么事件循环对于两个队列的优先级是怎么样的呢?
1.main script中的代码优先执行(编写的顶层script代码);
2.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
我们来看一个面试题:执行结果如何?
setTimeout(function () {
console.log("set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("pr1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("set2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
执行结果:
pr1
2
then1
queueMicrotask1
then3
set1
then2
then4
set2
async、await是Promise的一个语法糖:
(resolve, reject) => {函数执行}
中的代码;then(res => {函数执行})
中的代码;今日头条的面试题:
async function async1 () {
console.log(‘async1 start‘)
await async2();
console.log(‘async1 end‘)
}
async function async2 () {
console.log(‘async2‘)
}
console.log(‘script start‘)
setTimeout(function () {
console.log(‘setTimeout‘)
}, 0)
async1();
new Promise (function (resolve) {
console.log(‘promise1‘)
resolve();
}).then (function () {
console.log(‘promise2‘)
})
console.log(‘script end‘)
执行结果如下:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
我们来看在很早就给大家展示的Node架构图:
libuv到底是什么呢?
libuv到底帮助我们做了什么事情呢?
如果我们希望在程序中对一个文件进行操作,那么我们就需要打开这个文件:通过文件描述符。
操作系统为我们提供了阻塞式调用
和非阻塞式调用
:
所以我们开发中的很多耗时操作,都可以基于这样的 非阻塞式调用
:
非阻塞方式的工作
;基于事件的回调机制
;但是非阻塞IO也会存在一定的问题:我们并没有获取到需要读取(我们以读取为例)的结果
那么这个轮训的工作由谁来完成呢?
libuv提供了一个线程池(Thread Pool):
阻塞和非阻塞,同步和异步有什么区别?
阻塞和非阻塞是对于被调用者来说的;
同步和异步是对于调用者来说的;
我们最前面就强调过,事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道:
但是一次完整的事件循环Tick分成很多个阶段:
setTimeout()
和 setInterval()
的调度回调函数。setImmediate()
回调函数在这里执行。socket.on(‘close‘, ...)
。我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:
但是,Node中的事件循环不只是 微任务队列
和 宏任务队列
:
微任务队列:
宏任务队列:
所以,在每一次事件循环的tick中,会按照如下顺序来执行代码:
面试题一:
async function async1() {
console.log(‘async1 start‘)
await async2()
console.log(‘async1 end‘)
}
async function async2() {
console.log(‘async2‘)
}
console.log(‘script start‘)
setTimeout(function () {
console.log(‘setTimeout0‘)
}, 0)
setTimeout(function () {
console.log(‘setTimeout2‘)
}, 300)
setImmediate(() => console.log(‘setImmediate‘));
process.nextTick(() => console.log(‘nextTick1‘));
async1();
process.nextTick(() => console.log(‘nextTick2‘));
new Promise(function (resolve) {
console.log(‘promise1‘)
resolve();
console.log(‘promise2‘)
}).then(function () {
console.log(‘promise3‘)
})
console.log(‘script end‘)
执行结果如下:
script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
面试题二:
setTimeout(() => {
console.log("setTimeout");
}, 0);
setImmediate(() => {
console.log("setImmediate");
});
执行结果:
情况一:
setTimeout
setImmediate
情况二:
setImmediate
setTimeout
为什么会出现不同的情况呢?
uv__next_timeout
的函数;int uv__next_timeout(const uv_loop_t* loop) {
const struct heap_node* heap_node;
const uv_timer_t* handle;
uint64_t diff;
// 计算距离当前时间节点最小的计时器
heap_node = heap_min(timer_heap(loop));
// 如果为空, 那么返回-1,表示为阻塞状态
if (heap_node == NULL)
return -1; /* block indefinitely */
// 如果计时器的时间小于当前loop的开始时间, 那么返回0
// 继续执行后续阶段, 并且开启下一次tick
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout <= loop->time)
return 0;
// 如果不大于loop的开始时间, 那么会返回时间差
diff = handle->timeout - loop->time;
if (diff > INT_MAX)
diff = INT_MAX;
return (int) diff;
}
和上面有什么关系呢?
情况一:如果事件循环开启的时间(ms)是小于 setTimeout
函数的执行时间的;
setImmediate
,第二次的tick中执行了timer中的 setTimeout
;情况二:如果事件循环开启的时间(ms)是大于 setTimeout
函数的执行时间的;
原文:https://www.cnblogs.com/107w/p/14879457.html