Node.js精进(2)——异步编程
<p> 虽然 Node.js 是单线程的,但是在融合了<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">libuv</span></span>后,使其有能力非常简单地就构建出高性能和可扩展的网络应用程序。</p><p> 下图是 Node.js 的简单架构图,基于 V8 和 libuv,其中 Node Bindings 为 JavaScript 和 C++ 搭建了一座沟通的桥梁,使得 JavaScript 可以访问 V8 和 libuv 向上层提供的 API。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202205/211606-20220523091731731-33557448.png" alt=""></p>
<p> 本系列所有的示例源码都已上传至Github,<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">点击此处</span></span>获取。</p>
<h1>一、术语解析</h1>
<p> 接下来会对几个与 Node.js 相关的术语做单独的解析,其中事件循环会单独细讲。</p>
<p><span style="font-size: 16px"><strong>1)libuv</strong></span></p>
<p> libuv 是一个事件驱动、非阻塞异步的 I/O 库,并且具备跨平台的能力,提供了一套事件循环(Event Loop)机制和一些核心工具,例如定时器、文件访问、线程池等。</p>
<p><span style="font-size: 16px"><strong>2)非阻塞异步的I/O</strong></span></p>
<p> 非阻塞是指线程不会被操作系统挂起,可以处理其他事情。</p>
<p> 异步是指调用者发起一个调用后,可以立即返回去做别的事。</p>
<p> I/O(Input/Output)即输入/输出,通常指数据在存储器或其他周边设备之间的输入和输出。</p>
<p> 它是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。</p>
<p> 将这些关键字组合在一起就能理解 Node.js 的高性能有一部分是通过避免等待 I/O(读写数据库、文件访问、网络调用等)响应来实现的。</p>
<p><span style="font-size: 16px"><strong>3)事件驱动</strong></span></p>
<p> 事件驱动是一种异步化的程序设计模型,通过用户动作、操作系统或应用程序产生的事件,来驱动程序完成某个操作。</p>
<p> 在 Node.js 中,事件主要来源于网络请求、文件读写等,它们会被事件循环所处理。</p>
<p> 在浏览器的 DOM 系统中使用的也非常广泛,例如为按钮绑定 click 事件,在用点击按钮时,弹出提示或提交表单等。</p>
<p><span style="font-size: 16px"><strong>4)单线程</strong></span></p>
<p> Node.js 的单线程是指运行 JavaScript 代码的主线程,网络请求或异步任务等都交给了底层的线程池中的线程来处理,其处理结果再通过事件循环向主线程告知。</p>
<p> 单线程意味着所有任务需要排队有序执行,如果出现一个计算时间很长的任务,那么就会占据主线程,其他任务只能等待,所以说 Node.js 不适合 CPU 密集型的场景。</p>
<p> 经过以上术语的分析可知,Node.js 的高性能和高并发离不开异步,所以有必要深入了解一下 Node.js 的异步原理。</p>
<h1>二、事件循环</h1>
<p> 当 Node.js 启动时会初始化事件循环,这是一个无限循环。</p>
<p> 下图是事件循环的一张运行机制图,新任务或完成 I/O 任务的回调,都会添加到事件循环中。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202205/211606-20220504164719432-1602060040.png" width="600"></p>
<p> 下面是按照运行优先级简化后的<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">六个循环阶段</span></span>。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> ┌───────────────────────────┐
┌─</span>><span style="color: rgba(0, 0, 0, 1)">│ timers │
│└─────────────┬─────────────┘
│┌─────────────┴─────────────┐
││ pending callbacks │
│└─────────────┬─────────────┘
│┌─────────────┴─────────────┐
││ idle, prepare │
│└─────────────┬─────────────┘ ┌───────────────┐
│┌─────────────┴─────────────┐ │ incoming: │
││ poll │</span><<span style="color: rgba(0, 0, 0, 1)">─────┤connections, │
│└─────────────┬─────────────┘ │ data, etc.│
│┌─────────────┴─────────────┐ └───────────────┘
││ check │
│└─────────────┬─────────────┘
│┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘</span></pre>
</div>
<p> 每个阶段都有一个 FIFO 回调队列,当队列耗尽或达到回调上限时,事件循环将进入下一阶段,如此往复。</p>
<ol>
<li>timers:执行由 setTimeout() 和 setInterval() 安排的回调。在此阶段内部,会维护一个定时器的小顶堆,按到期时间排序,先到期的先运行。</li>
<li>pending callbacks:处理上一轮循环未执行的 I/O 回调,例如网络、I/O 等异常时的回调。</li>
<li>idle,prepare:仅 Node 内部使用。</li>
<li>poll:执行与 I/O 相关的回调,除了关闭回调、定时器调度的回调和 setImmediate() , 适当的条件下 Node 将阻塞在这里。</li>
<li>check:调用 setImmediate() 回调。</li>
<li>close callbacks:关闭回调,例如 socket.on("close", callback)。</li>
</ol>
<p> 在<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">deps/uv/src/unix/core.c</span></span>文件中声明了事件循环的核心代码,旁边还有个 win 目录,应该就是指 Windows 系统中 libuv 相关的处理。</p>
<p> 其实事件循环就是一个大的 while 循环 ,具体如下所示。</p>
<p> 代码中的 UV_RUN_ONCE 就是上文 poll 阶段中的适当的条件,在每次循环结束前,执行完 close callbacks 阶段后,会再执行一次已到期的定时器。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">int</span> uv__loop_alive(<span style="color: rgba(0, 0, 255, 1)">const</span> uv_loop_t*<span style="color: rgba(0, 0, 0, 1)"> loop) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> uv__has_active_handles(loop) ||<span style="color: rgba(0, 0, 0, 1)">
uv__has_active_reqs(loop) </span>||<span style="color: rgba(0, 0, 0, 1)">
loop</span>->closing_handles !=<span style="color: rgba(0, 0, 0, 1)"> NULL;
}
</span><span style="color: rgba(0, 0, 255, 1)">int</span> uv_run(uv_loop_t*<span style="color: rgba(0, 0, 0, 1)"> loop, uv_run_mode mode) {
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> timeout;
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> r;
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> ran_pending;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查事件循环中是否还有待处理的handle、request、closing_handles是否为NULL</span>
r =<span style="color: rgba(0, 0, 0, 1)"> uv__loop_alive(loop);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新事件循环时间戳</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">r)
uv__update_time(loop);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 启动事件循环</span>
<span style="color: rgba(0, 0, 255, 1)">while</span> (r != <span style="color: rgba(128, 0, 128, 1)">0</span> && loop->stop_flag == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
uv__update_time(loop);
uv__run_timers(loop); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> timers阶段,执行已到期的定时器</span>
ran_pending = uv__run_pending(loop);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> pending阶段</span>
uv__run_idle(loop); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> idle阶段</span>
uv__run_prepare(loop);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> prepare阶段</span>
<span style="color: rgba(0, 0, 0, 1)">
timeout </span>= <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> ((mode == UV_RUN_ONCE && !ran_pending) || mode ==<span style="color: rgba(0, 0, 0, 1)"> UV_RUN_DEFAULT)
timeout </span>=<span style="color: rgba(0, 0, 0, 1)"> uv_backend_timeout(loop);
uv__io_poll(loop, timeout); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> poll阶段</span>
<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> Run one final update on the provider_idle_time in case uv__io_poll
* returned because the timeout expired, but no events were received. This
* call will be ignored if the provider_entry_time was either never set (if
* the timeout == 0) or was already updated b/c an event was received.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
uv__metrics_update_idle_time(loop);
uv__run_check(loop); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> check阶段</span>
uv__run_closing_handles(loop);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> close阶段</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (mode ==<span style="color: rgba(0, 0, 0, 1)"> UV_RUN_ONCE) {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 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.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
uv__update_time(loop);
uv__run_timers(loop); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行已到期的定时器</span>
<span style="color: rgba(0, 0, 0, 1)"> }
r </span>=<span style="color: rgba(0, 0, 0, 1)"> uv__loop_alive(loop);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在 UV_RUN_ONCE 和 UV_RUN_NOWAIT 模式中,跳出当前循环</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (mode == UV_RUN_ONCE || mode ==<span style="color: rgba(0, 0, 0, 1)"> UV_RUN_NOWAIT)
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> The if statement lets gcc compile it to a conditional store. Avoids
* dirtying a cache line.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (loop->stop_flag != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
loop</span>->stop_flag = <span style="color: rgba(128, 0, 128, 1)">0</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 标记当前的 stop_flag 为 0,表示跑完这轮,事件循环就结束了</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> r;
}</span></pre>
</div>
<p><span style="font-size: 16px"><strong>1)setTimeout 和 setImmediate</strong></span></p>
<p> setTimeout 会在最前面的 timers 阶段被执行,而 setImmediate 会在 check 阶段被执行。</p>
<p> 但在下面的示例中,timeout 和 immediate 的打印顺序是不确定的。</p>
<p> 在 setTimeout() <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">官方文档</span></span>中曾提到,当延迟时间大于 2147483647(24.8天) 或小于 1 时,将默认被设为 1。</p>
<p> 所以下面的 setTimeout(callback, 0) 相当于 setTimeout(callback, 1)。</p>
<p> 虽然在源码中会先运行 uv__run_timers(),但是由于上一次的循环耗时可能超过 1ms,也可能小于 1ms,所以定时器有可能还未到期。</p>
<p> 如此的话,就会造成打印顺序的不确定性,上述分析过程<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">参考了此处</span></span>。</p>
<div class="cnblogs_code">
<pre>setTimeout(() =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'timeout'<span style="color: rgba(0, 0, 0, 1)">)
}, </span>0<span style="color: rgba(0, 0, 0, 1)">);
setImmediate(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'immediate'<span style="color: rgba(0, 0, 0, 1)">)
});</span></pre>
</div>
<p> 如果将 setTimeout() 和 setImmediate() 注册到 I/O 回调中运行,那么顺序就是确定的,先 immediate 再 timeout。</p>
<div class="cnblogs_code">
<pre>const fs = require('fs'<span style="color: rgba(0, 0, 0, 1)">)
fs.readFile(__filename, () </span>=><span style="color: rgba(0, 0, 0, 1)"> {
setTimeout(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'timeout'<span style="color: rgba(0, 0, 0, 1)">);
}, </span>0<span style="color: rgba(0, 0, 0, 1)">)
setImmediate(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'immediate'<span style="color: rgba(0, 0, 0, 1)">)
})
});</span></pre>
</div>
<p> 这是因为 readFile() 的回调会在 poll 阶段运行,而在 uv__io_poll() 之后,就会立即执行 uv__run_check(),从而就能保证先打印 immediate 。</p>
<p> 在自己的日常工作中,曾使用过一个基于 setTimeout() 的定时任务库:<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">node-schedule</span></span>。</p>
<p> 由于延迟时间最长为 24.8 天,所以该库巧妙的运用了一个递归来弥补时间的上限。</p>
<div class="cnblogs_code">
<pre>Timeout.prototype.start = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.after <=<span style="color: rgba(0, 0, 0, 1)"> TIMEOUT_MAX) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.timeout = setTimeout(<span style="color: rgba(0, 0, 255, 1)">this</span>.listener, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.after)
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> self = <span style="color: rgba(0, 0, 255, 1)">this</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.timeout = setTimeout(<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
self.after </span>-=<span style="color: rgba(0, 0, 0, 1)"> TIMEOUT_MAX
self.start()
}, TIMEOUT_MAX)
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.unreffed) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.timeout.unref()
}
}</span></pre>
</div>
<p><span style="font-size: 16px"><strong>2)与浏览器中的事件循环的差异</strong></span></p>
<p> 在浏览器的事件循环中,没有那么细的循环阶段,不过有两个非常重要的概念,那就是宏任务和微任务。</p>
<p> 宏任务包括 setTimeout()、setInterval()、requestAnimationFrame、Ajax、fetch()、脚本标签代码等。</p>
<p> 微任务包括 Promise.then()、MutationObserver。</p>
<p> 在 Node.js 中,<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">process.nextTick()</span></span>是微任务的一种,setTimeout()、setInterval()、setImmediate() 等都属于宏任务。</p>
<p> 在 Node版本 < 11 时,执行完一个阶段的所有任务后,再执行process.nextTick(),最后是其他微任务。</p>
<p> 可以这样理解,process.nextTick() 维护了一个独立的队列,不存在于事件循环的任何阶段,而是在各个阶段切换的间隙执行。</p>
<p> 即从一个阶段切换到下个阶段前执行,执行时机如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> ┌───────────────────────────┐
┌─</span>><span style="color: rgba(0, 0, 0, 1)">│ timers │
│└─────────────┬─────────────┘
│ nextTickQueue
│┌─────────────┴─────────────┐
││ pending callbacks │
│└─────────────┬─────────────┘
│ nextTickQueue
│┌─────────────┴─────────────┐
││ idle, prepare │
│└─────────────┬─────────────┘
nextTickQueue nextTickQueue
│┌─────────────┴─────────────┐
││ poll │
│└─────────────┬─────────────┘
│ nextTickQueue
│┌─────────────┴─────────────┐
││ check │
│└─────────────┬─────────────┘
│ nextTickQueue
│┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘</span></pre>
</div>
<p> 但是在 Node 版本 >= 11 之后,会处理的和浏览器一样,也是每执行完一个宏任务,就将其微任务也一并完成。</p>
<p> 在下面这个示例中, setTimeout() 内先声明 then(),再声明 process.nextTick(),最后执行一条打印语句。</p>
<p> 接着在 setTimeout() 之后再次声明了 process.nextTick()。 </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> setTimeout</span>
setTimeout(() =><span style="color: rgba(0, 0, 0, 1)"> {
Promise.resolve().then(</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">() {
console.log(</span>'promise'<span style="color: rgba(0, 0, 0, 1)">);
});
process.nextTick(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'setTimeout nextTick'<span style="color: rgba(0, 0, 0, 1)">);
});
console.log(</span>'setTimeout'<span style="color: rgba(0, 0, 0, 1)">);
}, </span>0<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> nextTick</span>
process.nextTick(() =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'nextTick'<span style="color: rgba(0, 0, 0, 1)">);
});</span></pre>
</div>
<p> 我本地运行的 Node 版本是 16,所以最终的打印顺序如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">nextTick
setTimeout
setTimeout nextTick
promise</span></pre>
</div>
<p> 外面的 process.nextTick() 要比 setTimeout() 先运行,里面的打印语句最先执行,然后是 process.nextTick(),最后是 then()。</p>
<p><span style="font-size: 16px"><strong>3)sleep()</strong></span></p>
<p> 有一道比较经典的题目是编写一个 sleep() 函数,实现线程睡眠,在日常开发中很容易就会遇到。</p>
<p> 搜集了多种实现函数,有些是同步,有些是异步。</p>
<p> 第一种是同步函数,创建一个循环,占用主线程,直至循环完毕,这种方式也叫循环空转,比较浪费CPU性能,不推荐。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sleep(ms) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> start = Date.now(), expire = start +<span style="color: rgba(0, 0, 0, 1)"> ms;
</span><span style="color: rgba(0, 0, 255, 1)">while</span> (Date.now() <<span style="color: rgba(0, 0, 0, 1)"> expire);
}</span></pre>
</div>
<p> 第二至第四种都是异步函数,本质上线程并没有睡眠,事件循环仍在运行,下面是 Promise + setTimeout() 组合实现的 sleep() 函数。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sleep(ms) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Promise(resolve =><span style="color: rgba(0, 0, 0, 1)"> setTimeout(resolve, ms));
}</span></pre>
</div>
<p> 第三种是利用 util 库的<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">promisify()</span></span>函数,返回一个 Promise 版本的定时器。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sleep(ms) {
const { promisify } </span>= require('util'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> promisify(setTimeout)(ms);
}</span></pre>
</div>
<p> 第四种是当 Node 版本 >= 15 时可以使用,在<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">timers库</span></span>中直接得到一个 Promise 版本的定时器。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sleep(ms) {
const { setTimeout } </span>= require('timers/promises'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> setTimeout(ms);
}</span></pre>
</div>
<p> 第五种是同步函数,可利用<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">Atomics.wait</span></span>阻塞事件循环,直至线程超时,实现细节在此不做说明了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sleep(ms) {
const sharedBuf </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SharedArrayBuffer(4<span style="color: rgba(0, 0, 0, 1)">);
const sharedArr </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Int32Array(sharedBuf);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Atomics.wait(sharedArr, 0, 0<span style="color: rgba(0, 0, 0, 1)">, ms);
}</span></pre>
</div>
<p> 还可以编写 C/C++ 插件,直接调用操作系统的 sleep() 函数,此处不做展开。</p>
<p> </p>
<p>参考资料:</p>
<p>Event Loop 事件循环源码 Node.js技术栈</p>
<p>nodejs真的是单线程吗?</p>
<p>Nodejs探秘:深入理解单线程实现高并发原理</p>
<p>什么是CPU密集型、IO密集型? libuv I/O</p>
<p>JavaScript 运行机制详解:再谈Event Loop</p>
<p>Node.js Event Loop 的理解 Timers,process.nextTick()</p>
<p>浏览器与Node的事件循环(Event Loop)有何区别?</p>
<p>Why is the EventLoop for Browsers and Node.js Designed This Way?</p>
<p>Node.js 事件循环 Phases of the Node JS Event Loop</p>
<p>如何实现线程睡眠?</p>
<p>nodejs中的并发编程</p><br><br>
来源:https://www.cnblogs.com/strick/p/16219489.html
頁:
[1]