Rumah >hujung hadapan web >tutorial js >Mari kita bincangkan secara mendalam tentang mekanisme pelaksanaan dan pelaksanaan asas bagi gelung tak segerak dan peristiwa Node
Nod pada asalnya dilahirkan untuk membina pelayan web berprestasi tinggi Sebagai masa jalan sebelah pelayan untuk JavaScript, ia mempunyai I/O tak segerak dipacu peristiwa , dan single-threading dan ciri-ciri lain. Model pengaturcaraan tak segerak berdasarkan gelung acara membolehkan Node mengendalikan konkurensi yang tinggi dan meningkatkan prestasi pelayan dengan sangat baik, kerana ia mengekalkan ciri-ciri berbenang tunggal JavaScript, Node tidak perlu menangani isu seperti penyegerakan keadaan dan. kebuntuan di bawah berbilang benang. Tiada overhed prestasi disebabkan oleh penukaran konteks benang. Berdasarkan ciri-ciri ini, Node mempunyai kelebihan yang wujud dalam prestasi tinggi dan konkurensi tinggi, dan pelbagai platform aplikasi rangkaian berkelajuan tinggi dan berskala boleh dibina berdasarkannya.
Artikel ini akan membincangkan mekanisme pelaksanaan dan pelaksanaan asas Node asynchronous dan gelung peristiwa, saya harap ia akan membantu anda.
Mengapa Node menggunakan tak segerak sebagai model pengaturcaraan terasnya?
Seperti yang dinyatakan sebelum ini, Node pada asalnya dicipta untuk membina pelayan web berprestasi tinggi Dengan mengandaikan bahawa terdapat beberapa set tugasan yang tidak berkaitan untuk diselesaikan dalam senario perniagaan, terdapat dua penyelesaian moden arus perdana:
<.> dan setTimeOut
Jenis ini operasi tiada kaitan dengan I/O. Jelas sekali asynchronous yang kita bicarakan merujuk kepada operasi yang berkaitan dengan I/O, iaitu, asynchronous I/O. setInterval
I/O tak segerak yang sempurna yang kami harapkan ialah aplikasi memulakan panggilan tidak menyekat secara berterusan. dalam I/O. O Selepas selesai, data boleh dihantar ke aplikasi melalui semaphore atau panggil balik.
Bagaimana untuk melaksanakan I/O tak segerak ini? Jawapannya ialah kolam benang.
Walaupun artikel ini selalu menyebut bahawa Node dilaksanakan dalam satu utas, satu utas di sini bermakna bahawa kod JavaScript dilaksanakan pada satu utas Untuk operasi I/O yang tiada kaitan dengan utama logik perniagaan Sebahagiannya, ia dilaksanakan dengan menjalankan dalam utas lain, yang tidak akan menjejaskan atau menyekat perjalanan utas utama Sebaliknya, ia boleh meningkatkan kecekapan pelaksanaan utas utama dan merealisasikan I/O tak segerak.
Melalui kumpulan utas, biarkan utas utama hanya membuat panggilan I/O dan biarkan utas lain melakukan penyekatan I/O atau I/O tidak menyekat serta teknologi pengundian untuk melengkapkan pemerolehan data, dan kemudian melalui benang Komunikasi memindahkan data yang diperolehi oleh I/O, yang dengan mudah melaksanakan I/O tak segerak:
Benang utama membuat panggilan I/O, dan kumpulan benang menjadikan I/ Panggilan O. /O operasi, selesaikan pemerolehan data, dan kemudian hantar data ke utas utama melalui komunikasi antara utas untuk menyelesaikan panggilan I/O Kemudiannya menggunakan fungsi panggil balik untuk mendedahkan data kepada pengguna, dan pengguna kemudian menggunakan Data ini digunakan untuk menyelesaikan operasi pada peringkat logik perniagaan Ini adalah proses I/O tak segerak yang lengkap dalam Node. Bagi pengguna, tidak perlu risau tentang butiran pelaksanaan yang menyusahkan lapisan asas Mereka hanya perlu memanggil API tak segerak yang dirangkumkan oleh Node dan meneruskan fungsi panggil balik yang mengendalikan logik perniagaan, seperti yang ditunjukkan di bawah:
const fs = require("fs"); fs.readFile('example.js', (data) => { // 进行业务逻辑的处理 });
Mekanisme pelaksanaan asas asynchronous Nodejs adalah berbeza di bawah platform yang berbeza: di bawah Windows, IOCP digunakan terutamanya untuk menghantar panggilan I/O ke kernel sistem dan mendapatkan operasi I/O yang lengkap daripada kernel, ditambah dengan gelung peristiwa , untuk melengkapkan proses I/O tak segerak ini dilaksanakan melalui epoll di bawah Linux melalui kqueue di bawah FreeBSD, dan melalui port Acara di bawah Solaris; Kumpulan benang disediakan secara langsung oleh kernel (IOCP) di bawah Windows, dan siri *nix
dilaksanakan oleh libuv itu sendiri.
Disebabkan oleh perbezaan antara platform Windows dan platform *nix
, Node menyediakan libuv sebagai lapisan enkapsulasi abstrak, supaya semua pertimbangan keserasian platform diselesaikan oleh lapisan ini, memastikan penyesuaian Nod atas dan lapisan bawah Kolam benang dan IOCP adalah bebas antara satu sama lain. Node akan menilai keadaan platform semasa penyusunan dan secara selektif menyusun fail sumber dalam direktori unix atau direktori win ke dalam program sasaran:
Di atas adalah pelaksanaan Node tak segerak.
(Saiz kumpulan benang boleh ditetapkan melalui pembolehubah persekitaran UV_THREADPOOL_SIZE
. Nilai lalai ialah 4. Pengguna boleh melaraskan saiz nilai ini berdasarkan situasi sebenar.)
Maka persoalannya ialah, dalam Selepas mendapat data yang diluluskan oleh kumpulan benang, bagaimana dan bila benang utama memanggil fungsi panggil balik? Jawapannya ialah gelung acara.
Memandangkan fungsi panggil balik digunakan untuk memproses data I/O, ia sudah semestinya melibatkan isu bila dan cara memanggil fungsi panggil balik. Dalam pembangunan sebenar, senario panggilan I/O tak segerak berbilang dan berbilang jenis sering terlibat Cara mengatur panggilan balik I/O tak segerak ini dan memastikan kemajuan teratur panggilan balik tak segerak Selain itu, sebagai tambahan kepada I/O tak segerak Selain /O, terdapat juga panggilan tak segerak bukan I/O seperti pemasa jenis API ini sangat masa nyata dan mempunyai keutamaan yang lebih tinggi. Bagaimana untuk menjadualkan panggilan balik dengan keutamaan yang berbeza?
Oleh itu, mesti ada mekanisme penjadualan untuk menyelaraskan tugas tak segerak dengan keutamaan dan jenis yang berbeza untuk memastikan tugasan ini berjalan dengan teratur pada urutan utama. Seperti pelayar, Node telah memilih gelung acara untuk melakukan pengangkatan berat ini.
Node membahagikan tugas kepada tujuh kategori mengikut jenis dan keutamaannya: Pemasa, Menunggu, Melahu, Sediakan, Tinjauan, Semak, Tutup. Untuk setiap jenis tugasan, terdapat baris gilir tugas masuk dahulu, keluar dahulu untuk menyimpan tugas dan panggilan baliknya (Pemasa disimpan dalam timbunan atas kecil). Berdasarkan tujuh jenis ini, Node membahagikan pelaksanaan gelung peristiwa kepada tujuh peringkat berikut:
Keutamaan pelaksanaan peringkat ini adalah yang tertinggi.
Pada peringkat ini, gelung peristiwa akan menyemak struktur data (timbunan minimum) yang menyimpan pemasa, melintasi pemasa di dalamnya, membandingkan masa semasa dan masa tamat satu demi satu, dan menentukan sama ada pemasa mempunyai tamat tempoh. Jika ya, , keluarkan fungsi panggil balik pemasa dan laksanakannya.
Peringkat ini akan melaksanakan panggilan balik apabila rangkaian, IO dan keabnormalan lain berlaku. Beberapa ralat yang dilaporkan oleh *nix
akan dikendalikan pada peringkat ini. Di samping itu, beberapa panggilan balik I/O yang sepatutnya dilaksanakan dalam fasa tinjauan pendapat kitaran sebelumnya akan ditangguhkan ke fasa ini.
Kedua-dua peringkat ini hanya digunakan di dalam gelung acara.
检索新的 I/O 事件;执行与 I/O 相关的回调(除了关闭回调、定时器调度的回调和 之外几乎所有回调setImmediate()
);节点会在适当的时候阻塞在这里。
poll,即轮询阶段是事件循环最重要的阶段,网络 I/O、文件 I/O 的回调都主要在这个阶段被处理。该阶段有两个主要功能:
计算该阶段应该阻塞和轮询 I/O 的时间。
处理 I/O 队列中的回调。
当事件循环进入 poll 阶段并且没有设置定时器时:
如果轮询队列不为空,则事件循环将遍历该队列,同步地执行它们,直到队列为空或达到可执行的最大数量。
如果轮询队列为空,则会发生另外两种情况之一:
如果有 setImmediate()
回调需要执行,则立即结束 poll 阶段,并进入 check 阶段以执行回调。
如果没有 setImmediate()
回调需要执行,事件循环将停留在该阶段以等待回调被添加到队列中,然后立即执行它们。在超时时间到达前,事件循环会一直停留等待。之所以选择停留在这里是因为 Node 主要是处理 IO 的,这样可以更及时地响应 IO。
一旦轮询队列为空,事件循环将检查已达到时间阈值的定时器。如果有一个或多个定时器达到时间阈值,事件循环将回到 timers 阶段以执行这些定时器的回调。
该阶段会依次执行 setImmediate()
的回调。
该阶段会执行一些关闭资源的回调,如 socket.on('close', ...)
。该阶段晚点执行也影响不大,优先级最低。
当 Node 进程启动时,它会初始化事件循环,执行用户的输入代码,进行相应异步 API 的调用、计时器的调度等等,然后开始进入事件循环:
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<p>事件循环的每一轮循环(通常被称为 tick),会按照如上给定的优先级顺序进入七个阶段的执行,每个阶段会执行一定数量的队列中的回调,之所以只执行一定数量而不全部执行完,是为了防止当前阶段执行时间过长,避免下一个阶段得不到执行。</p><p>OK,以上就是事件循环的基本执行流程。现在让我们来看另外一个问题。</p><p>对于以下这个场景:</p><pre class="brush:php;toolbar:false">const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
当服务成功绑定到 8000 端口,即 listen()
成功调用时,此时 listening
事件的回调还没有绑定,因此端口成功绑定后,我们所传入的 listening
事件的回调并不会执行。
再思考另外一个问题,我们在开发中可能会有一些需求,如处理错误、清理不需要的资源等等优先级不是那么高的任务,如果以同步的方式执行这些逻辑,就会影响当前任务的执行效率;如果以异步的方式,比如以回调的形式传入 setImmediate()
又无法保证它们的执行时机,实时性不高。那么要如何处理这些逻辑呢?
基于这几个问题,Node 参考了浏览器,也实现了一套微任务的机制。在 Node 中,除了调用 new Promise().then()
所传入的回调函数会被封装成微任务外,process.nextTick()
的回调也会被封装成微任务,并且后者的执行优先级比前者高。
有了微任务后,事件循环的执行流程又是怎么样的呢?换句话说,微任务的执行时机在什么时候?
在 node 11 及 11 之后的版本,一旦执行完一个阶段里的一个任务就立刻执行微任务队列,清空该队列。
在 node11 之前执行完一个阶段后才开始执行微任务。
因此,有了微任务后,事件循环的每一轮循环,会先执行 timers 阶段的一个任务,然后按照先后顺序清空 process.nextTick()
和 new Promise().then()
的微任务队列,接着继续执行 timers 阶段的下一个任务或者下一个阶段,即 pending 阶段的一个任务,按照这样的顺序以此类推。
利用 process.nextTick()
,Node 就可以解决上面的端口绑定问题:在 listen()
方法内部,listening
事件的发出会被封装成回调传入 process.nextTick()
中,如下伪代码所示:
function listen() { // 进行监听端口的操作 ... // 将 `listening` 事件的发出封装成回调传入 `process.nextTick()` 中 process.nextTick(() => { emit('listening'); }); };
在当前代码执行完毕后便会开始执行微任务,从而发出 listening
事件,触发该事件回调的调用。
由于异步本身的不可预知性和复杂性,在使用 Node 提供的异步 API 的过程中,尽管我们已经掌握了事件循环的执行原理,但是仍可能会有一些不符合直觉或预期的现象产生。
比如定时器(setTimeout
、setImmediate
)的执行顺序会因为调用它们的上下文而有所不同。如果两者都是从顶层上下文中调用的,那么它们的执行时间取决于进程或机器的性能。
我们来看以下这个例子:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
以上代码的执行结果是什么呢?按照我们刚才对事件循环的描述,你可能会有这样的答案:由于 timers 阶段会比 check 阶段先执行,因此 setTimeout()
的回调会先执行,然后再执行 setImmediate()
的回调。
实际上,这段代码的输出结果是不确定的,可能先输出 timeout,也可能先输出 immediate。这是因为这两个定时器都是在全局上下文中调用的,当事件循环开始运行并执行到 timers 阶段时,当前时间可能大于 1 ms,也可能不足 1 ms,具体取决于机器的执行性能,因此 setTimeout()
在第一个 timers 阶段是否会被执行实际上是不确定的,因此才会出现不同的输出结果。
(当 delay
(setTimeout
的第二个参数)的值大于 2147483647
或小于 1
时, delay
会被设置为 1
。)
我们接着看下面这段代码:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
可以看到,在这段代码中两个定时器都被封装成回调函数传入 readFile
中,很明显当该回调被调用时当前时间肯定大于 1 ms 了,所以 setTimeout
的回调会比 setImmediate
的回调先得到调用,因此打印结果为:timeout immediate
。
以上是在使用 Node 时需要注意的与定时器相关的事项。除此之外,还需注意 process.nextTick()
与 new Promise().then()
还有 setImmediate()
的执行顺序,由于这部分比较简单,前面已经提到过,就不再赘述了。
文章开篇从为什么要异步、如何实现异步两个角度出发,较详细地阐述了 Node 事件循环的实现原理,并提到一些需要注意的相关事项,希望对你有所帮助。
更多node相关知识,请访问:nodejs 教程!
Atas ialah kandungan terperinci Mari kita bincangkan secara mendalam tentang mekanisme pelaksanaan dan pelaksanaan asas bagi gelung tak segerak dan peristiwa Node. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!