Rumah  >  Artikel  >  hujung hadapan web  >  Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

青灯夜游
青灯夜游ke hadapan
2021-12-31 19:18:551513semak imbas

Node.js ialah bahasa berbenang tunggal yang mengendalikan operasi I/O tanpa sekatan melalui gelung peristiwa. Artikel berikut akan memberi anda pemahaman terperinci tentang gelung acara dalam Nod Saya harap ia akan membantu anda!

Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

Node.js Sebagai masa jalan pelayan JavaScript, ia terutamanya berkaitan dengan rangkaian dan fail, tanpa fasa pemaparan gelung acara dalam penyemak imbas .

Terdapat spesifikasi HTML dalam penyemak imbas untuk menentukan model pemprosesan gelung peristiwa, yang kemudiannya dilaksanakan oleh pelbagai pengeluar penyemak imbas. Takrifan dan pelaksanaan gelung peristiwa dalam Node.js datang daripada Libuv.

Libuv direka bentuk mengikut model I/O tak segerak dipacu peristiwa dan pada asalnya ditulis untuk Node.js, menyediakan perpustakaan sokongan merentas platform. Rajah di bawah menunjukkan komponen Rangkaian I/O ialah bahagian yang berkaitan dengan pemprosesan rangkaian Terdapat juga operasi fail dan DNS di bahagian bawah, epoll, kqueue, port acara dan IOCP adalah pelaksanaan asas yang berbeza. sistem pengendalian.

Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

Enam peringkat gelung acara

Apabila Node.js bermula, ia memulakan gelung acara dan memproses skrip yang disediakan , kod segerak ditolak ke tindanan dan dilaksanakan secara langsung Tugas tak segerak (permintaan rangkaian, operasi fail, pemasa, dll.) akan memindahkan operasi ke latar belakang untuk diproses oleh kernel sistem selepas memanggil API dan menghantar fungsi panggil balik. Kebanyakan kernel semasa berbilang benang Apabila salah satu operasi selesai, kernel memberitahu Node.js untuk menambah fungsi panggil balik pada barisan pengundian dan menunggu peluang untuk dilaksanakan.

Sebelah kiri gambar di bawah ialah perihalan proses gelung acara pada tapak web rasmi Node.js, dan sebelah kanan ialah perihalan Node.js pada tapak web rasmi Libuv. Kedua-duanya adalah pengenalan kepada gelung acara. Tidak semua orang dapat melihatnya Kod sumber ini biasanya merupakan dokumen rujukan langsung untuk mempelajari tentang gelung acara pembelajaran.

Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

Gelung acara yang dipaparkan pada tapak web rasmi Node.js di sebelah kiri dibahagikan kepada 6 peringkat Setiap peringkat mempunyai baris gilir FIFO (masuk dahulu, keluar dahulu) untuk dilaksanakan fungsi panggil balik Urutan keutamaan pelaksanaan antara peringkat masih jelas.

Sebelah kanan menerangkannya dengan lebih terperinci Sebelum mengulangi gelung peristiwa, tentukan dahulu sama ada gelung berada dalam keadaan aktif (terdapat I/O tak segerak, pemasa, dsb. Jika ia berada dalam keadaan aktif, mulakan lelaran, jika tidak Gelung akan keluar serta-merta.

Setiap peringkat dibincangkan secara berasingan di bawah.

pemasa (fasa pemasa)

Mula-mula gelung acara memasuki fasa pemasa, yang mengandungi dua API setTimeout(cb, ms) dan setInterval(cb, ms) ) Yang pertama dilaksanakan sekali sahaja, dan yang terakhir dilaksanakan berulang kali.

Peringkat ini menyemak sama ada terdapat fungsi pemasa tamat tempoh Jika ada, fungsi panggil balik pemasa tamat tempoh dilaksanakan Sama seperti dalam penyemak imbas, masa tunda yang dilalui oleh fungsi pemasa sentiasa lebih lama daripada yang kita jangkakan . Kemudian, ia akan terjejas oleh sistem pengendalian atau fungsi panggil balik lain yang sedang berjalan.

Sebagai contoh, dalam contoh berikut kami menetapkan fungsi pemasa dan menjangkakan ia dilaksanakan selepas 1000 milisaat.

const now = Date.now();
setTimeout(function timer1(){
  log(`delay ${Date.now() - now} ms`);
}, 1000);
setTimeout(function timer2(){
 log(`delay ${Date.now() - now} ms`);
}, 5000);
someOperation();

function someOperation() {
  // sync operation...
  while (Date.now() - now < 3000) {}
}

Selepas memanggil fungsi asynchronous setTimeout, program kemudian melaksanakan fungsi someOperation() Beberapa operasi yang memakan masa di bahagian tengah menggunakan kira-kira 3000ms selepas menyelesaikan operasi segerak ini, ia memasuki gelung peristiwa dan mula-mula menyemak pemasaan sama ada terdapat tugasan tamat tempoh dalam fasa pemasa, skrip pemasa disimpan dalam memori timbunan dalam tertib masa tunda menaik Mula-mula, fungsi pemasa dengan masa tamat terkecil dikeluarkan untuk menyemak Jika nowTime - timerTaskRegisterTime > kelewatan dikeluarkan Fungsi panggil balik dilaksanakan, jika tidak, semakan diteruskan Apabila fungsi pemasa yang belum tamat tempoh dikesan atau bilangan kebergantungan sistem maksimum dicapai, sistem beralih ke seterusnya. pentas.

Dalam contoh kami, anggap bahawa masa semasa selepas melaksanakan fungsi someOperation() ialah T 3000:

Semak fungsi pemasa1, masa semasa ialah T 3000 - T > lebih daripada jangkaan Selepas masa tunda, keluarkan fungsi panggil balik dan teruskan menyemak.

Semak fungsi pemasa2, masa semasa ialah T 3000 - T

panggil balik belum selesai

Selepas fasa pemasa selesai, gelung acara memasuki fasa panggil balik yang belum selesai, di mana panggilan balik I/O ditinggalkan daripada pusingan acara sebelumnya gelung dilaksanakan. Menurut dokumentasi Libuv: Dalam kebanyakan kes, semua panggilan balik I/O dipanggil serta-merta selepas pengundian untuk I/O, walau bagaimanapun, dalam beberapa kes, panggilan panggil balik tersebut ditangguhkan sehingga lelaran gelung seterusnya. Selepas mendengarnya, rasanya lebih seperti tinggalan dari peringkat sebelumnya.

idle, prepare

idle, prepare 阶段是给系统内部使用,idle 这个名字很迷惑,尽管叫空闲,但是在每次的事件循环中都会被调用,当它们处于活动状态时。这一块的资料介绍也不是很多。略...

poll

poll 是一个重要的阶段,这里有一个概念观察者,有文件 I/O 观察者,网络 I/O 观察者等,它会观察是否有新的请求进入,包含读取文件等待响应,等待新的 socket 请求,这个阶段在某些情况下是会阻塞的。

阻塞 I/O 超时时间

在阻塞 I/O 之前,要计算它应该阻塞多长时间,参考 Libuv 文档上的一些描述,以下这些是它计算超时时间的规则:

如果循环使用 UV_RUN_NOWAIT 标志运行、超时为 0。

如果循环将要停止(uv_stop() 被调用),超时为 0。

如果没有活动的 handlers 或 request,超时为 0。

如果有任何 idle handlers 处于活动状态,超时为 0。

如果有任何待关闭的 handlers,超时为 0。

如果以上情况都没有,则采用最近定时器的超时时间,或者如果没有活动的定时器,则超时时间为无穷大,poll 阶段会一直阻塞下去。

示例一

很简单的一段代码,我们启动一个 Server,现在事件循环的其它阶段没有要处理的任务,它会在这里等待下去,直到有新的请求进来。

const http = require(&#39;http&#39;);
const server = http.createServer();
server.on(&#39;request&#39;, req => {
  console.log(req.url);
})
server.listen(3000);

示例二

结合阶段一的定时器,在看个示例,首先启动 app.js 做为服务端,模拟延迟 3000ms 响应,这个只是为了配合测试。再运行 client.js 看下事件循环的执行过程:

首先程序调用了一个在 1000ms 后超时的定时器。

之后调用异步函数 someAsyncOperation() 从网络读取数据,我们假设这个异步网路读取需要 3000ms。

当事件循环开始时先进入 timer 阶段,发现没有超时的定时器函数,继续向下执行。

期间经过 pending callbacks -> idle,prepare 当进入 poll 阶段,此时的 http.get() 尚未完成,它的队列为空,参考上面 poll 阻塞超时时间规则,事件循环机制会检查最快到达阀值的计时器,而不是一直在这里等待下去。

当大约过了 1000ms 后,进入下一次事件循环进入定时器,执行到期的定时器回调函数,我们会看到日志 setTimeout run after 1003 ms。

在定时器阶段结束之后,会再次进入 poll 阶段,继续等待。

// client.js
const now = Date.now();
setTimeout(() => log(`setTimeout run after ${Date.now() - now} ms`), 1000);
someAsyncOperation();
function someAsyncOperation() {
  http.get(&#39;http://localhost:3000/api/news&#39;, () => {
    log(`fetch data success after ${Date.now() - now} ms`);
  });
}

// app.js
const http = require(&#39;http&#39;);
http.createServer((req, res) => {
  setTimeout(() => { res.end(&#39;OK!&#39;) }, 3000);
}).listen(3000);

当 poll 阶段队列为空时,并且脚本被 setImmediate() 调度过,此时,事件循环也会结束 poll 阶段,进入下一个阶段 check。

check

check 阶段在 poll 阶段之后运行,这个阶段包含一个 API setImmediate(cb) 如果有被 setImmediate 触发的回调函数,就取出执行,直到队列为空或达到系统的最大限制。

setTimeout VS setImmediate

拿 setTimeout 和 setImmediate 对比,这是一个常见的例子,基于被调用的时机和定时器可能会受到计算机上其它正在运行的应用程序影响,它们的输出顺序,不总是固定的。

setTimeout(() => log(&#39;setTimeout&#39;));
setImmediate(() => log(&#39;setImmediate&#39;));

// 第一次运行
setTimeout
setImmediate

// 第二次运行
setImmediate
setTimeout

setTimeout VS setImmediate VS fs.readFile

但是一旦把这两个函数放入一个 I/O 循环内调用,setImmediate 将总是会被优先调用。因为 setImmediate 属于 check 阶段,在事件循环中总是在 poll 阶段结束后运行,这个顺序是确定的。

fs.readFile(__filename, () => {
  setTimeout(() => log(&#39;setTimeout&#39;));
  setImmediate(() => log(&#39;setImmediate&#39;));
})

close callbacks

在 Libuv 中,如果调用关闭句柄 uv_close(),它将调用关闭回调,也就是事件循环的最后一个阶段 close callbacks。

这个阶段的工作更像是做一些清理工作,例如,当调用 socket.destroy(),'close' 事件将在这个阶段发出,事件循环在执行完这个阶段队列里的回调函数后,检查循环是否还 alive,如果为 no 退出,否则继续下一次新的事件循环。

包含 Microtask 的事件循环流程图

在浏览器的事件循环中,把任务划分为 Task、Microtask,在 Node.js 中是按照阶段划分的,上面我们介绍了 Node.js 事件循环的 6 个阶段,给用户使用的主要是 timer、poll、check、close callback 四个阶段,剩下两个由系统内部调度。这些阶段所产生的任务,我们可以看做 Task 任务源,也就是常说的 “Macrotask 宏任务”。

通常我们在谈论一个事件循环时还会包含 Microtask,Node.js 里的微任务有 Promise、还有一个也许很少关注的函数 queueMicrotask,它是在 Node.js v11.0.0 之后被实现的,参见 PR/22951。

Node.js 中的事件循环在每一个阶段执行后,都会检查微任务队列中是否有待执行的任务。

Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node

Node.js 11.x 前后差异

Node.js 在 v11.x 前后,每个阶段如果即存在可执行的 Task 又存在 Microtask 时,会有一些差异,先看一段代码:

setImmediate(() => {
  log(&#39;setImmediate1&#39;);
  Promise.resolve(&#39;Promise microtask 1&#39;)
    .then(log);
});
setImmediate(() => {
  log(&#39;setImmediate2&#39;);
  Promise.resolve(&#39;Promise microtask 2&#39;)
    .then(log);
});

在 Node.js v11.x 之前,当前阶段如果存在多个可执行的 Task,先执行完毕,再开始执行微任务。基于 v10.22.1 版本运行结果如下:

setImmediate1
setImmediate2
Promise microtask 1
Promise microtask 2

在 Node.js v11.x 之后,当前阶段如果存在多个可执行的 Task,先取出一个 Task 执行,并清空对应的微任务队列,再次取出下一个可执行的任务,继续执行。基于 v14.15.0 版本运行结果如下:

setImmediate1
Promise microtask 1
setImmediate2
Promise microtask 2

在 Node.js v11.x 之前的这个执行顺序问题,被认为是一个应该要修复的 Bug 在 v11.x 之后并修改了它的执行时机,和浏览器保持了一致,详细参见 issues/22257 讨论。

特别的 process.nextTick()

Node.js 中还有一个异步函数 process.nextTick(),从技术上讲它不是事件循环的一部分,它在当前操作完成后处理。如果出现递归的 process.nextTick() 调用,这将会很糟糕,它会阻断事件循环。

如下例所示,展示了一个 process.nextTick() 递归调用示例,目前事件循环位于 I/O 循环内,当同步代码执行完成后 process.nextTick() 会被立即执行,它会陷入无限循环中,与同步的递归不同的是,它不会触碰 v8 最大调用堆栈限制。但是会破坏事件循环调度,setTimeout 将永远得不到执行。

fs.readFile(__filename, () => {
  process.nextTick(() => {
    log(&#39;nextTick&#39;);
    run();
    function run() {
      process.nextTick(() => run());
    }
  });
  log(&#39;sync run&#39;);
  setTimeout(() => log(&#39;setTimeout&#39;));
});

// 输出
sync run
nextTick

将 process.nextTick 改为 setImmediate 虽然是递归的,但它不会影响事件循环调度,setTimeout 在下一次事件循环中被执行。

fs.readFile(__filename, () => {
  process.nextTick(() => {
    log(&#39;nextTick&#39;);
    run();
    function run() {
      setImmediate(() => run());
    }
  });
  log(&#39;sync run&#39;);
  setTimeout(() => log(&#39;setTimeout&#39;));
});

// 输出
sync run
nextTick
setTimeout

process.nextTick 是立即执行,setImmediate 是在下一次事件循环的 check 阶段执行。但是,它们的名字着实让人费解,也许会想这两个名字交换下比较好,但它属于遗留问题,也不太可能会改变,因为这会破坏 NPM 上大部分的软件包。

在 Node.js 的文档中也建议开发者尽可能的使用 setImmediate(),也更容易理解。

更多node相关知识,请访问:nodejs 教程!!

Atas ialah kandungan terperinci Artikel untuk mengetahui lebih lanjut tentang gelung acara dalam Node. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam