Saya baru saja menonton video berikut: Pengantar Node.js dan masih tidak mengerti bagaimana Anda mendapatkan manfaat kecepatan.
Terutama, pada satu titik Ryan Dahl (pencipta Node.js ') mengatakan bahwa Node.js adalah event-loop berdasarkan bukan berbasis thread. Utas mahal dan hanya boleh diserahkan kepada para ahli pemrograman konkuren untuk dimanfaatkan.
Kemudian, ia kemudian menunjukkan tumpukan arsitektur Node.js yang memiliki implementasi C yang mendasarinya yang memiliki kumpulan Thread sendiri secara internal. Jadi jelas pengembang Node.js tidak akan pernah memulai utas mereka sendiri atau menggunakan kumpulan utas secara langsung ... mereka menggunakan panggilan balik async. Sejauh itu saya mengerti.
Apa yang saya tidak mengerti adalah titik bahwa Node.js masih menggunakan utas ... itu hanya menyembunyikan implementasi jadi bagaimana ini lebih cepat jika 50 orang meminta 50 file (tidak saat ini dalam memori) baik maka tidak diperlukan 50 utas ?
Satu-satunya perbedaan adalah bahwa karena dikelola secara internal, pengembang Node.js tidak harus mengkodekan detail berulir tetapi di bawahnya masih menggunakan utas untuk memproses permintaan file IO (pemblokiran).
Jadi bukankah Anda benar-benar hanya mengambil satu masalah (threading) dan menyembunyikannya sementara masalah itu masih ada: terutama beberapa utas, pengalihan konteks, kunci-mati ... dll?
Pasti ada beberapa detail yang masih belum saya mengerti di sini.
sumber
select()
lebih cepat daripada pertukaran konteks thread.Jawaban:
Sebenarnya ada beberapa hal berbeda yang digabungkan di sini. Tetapi itu dimulai dengan meme bahwa utas sangat sulit. Jadi jika mereka sulit, Anda lebih mungkin, ketika menggunakan utas untuk 1) rusak karena bug dan 2) tidak menggunakannya seefisien mungkin. (2) adalah yang Anda tanyakan.
Pikirkan salah satu contoh yang dia berikan, di mana permintaan masuk dan Anda menjalankan beberapa permintaan, dan kemudian melakukan sesuatu dengan hasil itu. Jika Anda menulisnya dengan cara prosedural standar, kode tersebut mungkin terlihat seperti ini:
Jika permintaan masuk menyebabkan Anda membuat utas baru yang menjalankan kode di atas, Anda akan memiliki utas duduk di sana, tidak melakukan apa-apa saat
query()
menjalankan. (Apache, menurut Ryan, menggunakan satu utas untuk memenuhi permintaan asli sedangkan nginx mengungguli itu dalam kasus yang dia bicarakan karena tidak.)Sekarang, jika Anda benar-benar pintar, Anda akan mengekspresikan kode di atas dengan cara di mana lingkungan bisa mati dan melakukan sesuatu yang lain saat Anda menjalankan kueri:
Ini pada dasarnya adalah apa yang dilakukan node.js. Anda pada dasarnya dekorasi - dengan cara yang nyaman karena bahasa dan lingkungan, maka poin tentang penutupan - kode Anda sedemikian rupa sehingga lingkungan dapat menjadi pintar tentang apa yang berjalan, dan kapan. Dengan cara itu, node.js bukanlah hal baru dalam arti bahwa ia menciptakan I / O yang tidak sinkron (tidak ada orang yang mengklaim sesuatu seperti ini), tetapi itu baru dalam cara yang diungkapkannya sedikit berbeda.
Catatan: ketika saya mengatakan bahwa lingkungan dapat menjadi pintar tentang apa yang berjalan dan kapan, khususnya yang saya maksud adalah bahwa utas yang digunakan untuk memulai beberapa I / O sekarang dapat digunakan untuk menangani beberapa permintaan lain, atau beberapa perhitungan yang dapat dilakukan secara paralel, atau memulai beberapa I / O paralel lainnya. (Saya tidak yakin simpul cukup canggih untuk memulai lebih banyak pekerjaan untuk permintaan yang sama, tetapi Anda mendapatkan idenya.)
sumber
Catatan! Ini jawaban lama. Meskipun masih benar dalam garis besar, beberapa detail mungkin telah berubah karena perkembangan pesat Node dalam beberapa tahun terakhir.
Itu menggunakan utas karena:
Untuk memalsukan IO non-pemblokiran, utas diperlukan: jangan memblokir IO dalam utas terpisah. Ini adalah solusi yang jelek dan menyebabkan banyak overhead.
Lebih buruk lagi di tingkat perangkat keras:
Ini benar-benar bodoh dan tidak efisien. Tapi setidaknya berhasil! Kita dapat menikmati Node.js karena menyembunyikan detail yang jelek dan rumit di balik arsitektur asinkron yang digerakkan oleh peristiwa.
Mungkin seseorang akan menerapkan O_NONBLOCK untuk file di masa mendatang? ...
Sunting: Saya membahas ini dengan seorang teman dan dia mengatakan kepada saya bahwa sebuah alternatif untuk utas adalah polling dengan select : tentukan batas waktu 0 dan lakukan IO pada deskriptor file yang dikembalikan (sekarang mereka dijamin tidak akan memblokir).
sumber
Saya khawatir saya "melakukan hal yang salah" di sini, jika demikian hapus saya dan saya minta maaf. Secara khusus, saya gagal melihat bagaimana saya membuat anotasi kecil yang rapi yang dibuat beberapa orang. Namun, saya memiliki banyak kekhawatiran / pengamatan untuk dilakukan pada utas ini.
1) Elemen yang dikomentari dalam pseudo-code di salah satu jawaban populer
pada dasarnya palsu. Jika utasnya adalah komputasi, maka itu bukan jempol memutar, itu melakukan pekerjaan yang diperlukan. Jika, di sisi lain, itu hanya menunggu penyelesaian IO, maka itu tidak menggunakan waktu CPU, inti dari infrastruktur kontrol thread di kernel adalah bahwa CPU akan menemukan sesuatu yang berguna untuk dilakukan. Satu-satunya cara untuk "memutar-mutar ibu jari Anda" seperti yang disarankan di sini adalah dengan membuat loop polling, dan tidak ada orang yang memiliki kode server web yang sebenarnya tidak cukup cakap untuk melakukan itu.
2) "Utas itu sulit", hanya masuk akal dalam konteks berbagi data. Jika pada dasarnya Anda memiliki utas independen seperti halnya saat menangani permintaan web independen, maka threading sederhana, Anda hanya perlu mengkode alur linear cara menangani satu pekerjaan, dan duduk dengan cukup mengetahui bahwa itu akan menangani beberapa permintaan, dan masing-masing akan mandiri secara efektif. Secara pribadi, saya berani mengatakan bahwa bagi sebagian besar programmer, mempelajari mekanisme penutupan / panggilan balik lebih kompleks daripada hanya mengkode versi thread top-to-bottom. (Tapi ya, jika Anda harus berkomunikasi di antara utas-utas, hidup menjadi sangat sulit dengan sangat cepat, tapi kemudian saya tidak yakin bahwa mekanisme penutupan / panggilan balik benar-benar mengubah itu, itu hanya membatasi pilihan Anda, karena pendekatan ini masih dapat dicapai dengan utas Bagaimanapun, itu
3) Sejauh ini, tidak ada yang memberikan bukti nyata mengapa satu jenis konteks tertentu akan lebih atau kurang memakan waktu daripada jenis lainnya. Pengalaman saya dalam membuat kernel multi-tasking (dalam skala kecil untuk embedded controller, tidak ada yang semewah OS "nyata") menunjukkan bahwa ini tidak akan terjadi.
4) Semua ilustrasi yang saya lihat sampai saat ini dimaksudkan untuk menunjukkan seberapa cepat Node dibandingkan webserer lain memiliki cacat yang sangat buruk, namun, mereka cacat dengan cara yang secara tidak langsung menggambarkan satu keuntungan yang pasti akan saya terima untuk Node (dan ini tidak berarti tidak berarti). Node tidak tampak seperti itu perlu (atau bahkan izin, sebenarnya) penyetelan. Jika Anda memiliki model ulir, Anda perlu membuat utas yang memadai untuk menangani beban yang diharapkan. Lakukan ini dengan buruk, dan Anda akan berakhir dengan kinerja yang buruk. Jika ada terlalu sedikit utas, maka CPU idle, tetapi tidak dapat menerima lebih banyak permintaan, membuat terlalu banyak utas, dan Anda akan membuang-buang memori kernel, dan dalam kasus lingkungan Java, Anda juga akan membuang-buang memori tumpukan utama . Sekarang, untuk Java, membuang heap adalah cara pertama, terbaik, untuk mengacaukan kinerja sistem, karena pengumpulan sampah yang efisien (saat ini, ini mungkin berubah dengan G1, tetapi tampaknya juri masih keluar pada titik itu setidaknya pada awal 2013) tergantung pada memiliki banyak tumpukan cadangan. Jadi, ada masalah, selaras dengan terlalu sedikit utas, Anda memiliki CPU yang menganggur dan throughput yang buruk, selaras dengan terlalu banyak, dan rusak dengan cara lain.
5) Ada cara lain di mana saya menerima logika klaim bahwa pendekatan Node "lebih cepat dengan desain", dan ini dia. Sebagian besar model utas menggunakan model sakelar konteks irisan waktu, berlapis di atas model preemptive yang lebih tepat (peringatan penilaian nilai :) dan lebih efisien (bukan penilaian nilai). Ini terjadi karena dua alasan, pertama, sebagian besar programmer tampaknya tidak memahami preemption prioritas, dan kedua, jika Anda belajar threading di lingkungan windows, ada rentang waktu apakah Anda suka atau tidak (tentu saja, ini memperkuat poin pertama terutama versi pertama Java menggunakan preemption prioritas pada implementasi Solaris, dan timeslicing di Windows. Karena kebanyakan programmer tidak mengerti dan mengeluh bahwa "threading tidak berfungsi di Solaris" mereka mengubah model menjadi kutu waktu di mana-mana). Bagaimanapun, intinya adalah bahwa penetapan waktu membuat switch konteks tambahan (dan mungkin tidak perlu). Setiap saklar konteks membutuhkan waktu CPU, dan waktu itu secara efektif dihilangkan dari pekerjaan yang dapat dilakukan pada pekerjaan nyata yang ada. Namun, jumlah waktu yang diinvestasikan dalam pengalihan konteks karena penentuan waktu tidak boleh lebih dari persentase yang sangat kecil dari keseluruhan waktu, kecuali jika sesuatu yang sangat aneh terjadi, dan tidak ada alasan saya dapat melihat untuk mengharapkan hal itu terjadi dalam suatu server web sederhana). Jadi, ya, saklar konteks berlebih yang terlibat dalam pengaturan waktu tidak efisien (dan ini tidak terjadi dan waktu itu secara efektif dihilangkan dari pekerjaan yang dapat dilakukan pada pekerjaan nyata yang ada. Namun, jumlah waktu yang diinvestasikan dalam pengalihan konteks karena penentuan waktu tidak boleh lebih dari persentase yang sangat kecil dari keseluruhan waktu, kecuali jika sesuatu yang sangat aneh terjadi, dan tidak ada alasan saya dapat melihat untuk mengharapkan hal itu terjadi dalam suatu server web sederhana). Jadi, ya, saklar konteks berlebih yang terlibat dalam pengaturan waktu tidak efisien (dan ini tidak terjadi dan waktu itu secara efektif dihilangkan dari pekerjaan yang dapat dilakukan pada pekerjaan nyata yang ada. Namun, jumlah waktu yang diinvestasikan dalam pengalihan konteks karena penentuan waktu tidak boleh lebih dari persentase yang sangat kecil dari keseluruhan waktu, kecuali jika sesuatu yang sangat aneh terjadi, dan tidak ada alasan saya dapat melihat untuk mengharapkan hal itu terjadi dalam suatu server web sederhana). Jadi, ya, saklar konteks berlebih yang terlibat dalam pengaturan waktu tidak efisien (dan ini tidak terjadikernel threads sebagai aturan, btw) tetapi perbedaannya adalah beberapa persen dari throughput, bukan jenis faktor bilangan bulat yang tersirat dalam klaim kinerja yang sering tersirat untuk Node.
Ngomong-ngomong, permintaan maaf untuk semua itu panjang dan kasar, tapi aku benar-benar merasa sejauh ini, diskusi belum membuktikan apa-apa, dan aku akan senang mendengar dari seseorang dalam salah satu situasi ini:
a) penjelasan nyata mengapa Node harus lebih baik (di luar dua skenario yang telah saya uraikan di atas, yang pertama (tuning yang buruk) Saya percaya adalah penjelasan nyata untuk semua tes yang saya lihat sejauh ini. ([edit ], sebenarnya, semakin saya memikirkannya, semakin saya bertanya-tanya apakah memori yang digunakan oleh sejumlah besar tumpukan mungkin signifikan di sini. Ukuran tumpukan standar untuk utas modern cenderung cukup besar, tetapi memori dialokasikan oleh sistem acara berbasis penutupan hanya akan menjadi apa yang dibutuhkan)
b) tolok ukur nyata yang benar-benar memberikan peluang yang adil ke server pilihan yang diulir. Setidaknya dengan cara itu, saya harus berhenti percaya bahwa klaim pada dasarnya salah;> (sunting) yang mungkin agak lebih kuat dari yang saya maksudkan, tetapi saya merasa bahwa penjelasan yang diberikan untuk manfaat kinerja tidak lengkap di terbaik, dan tolok ukur yang ditunjukkan tidak masuk akal).
Ceria, Toby
sumber
open()
tidak dapat dibuat non-pemblokiran?). Dengan cara ini, ini mengamortisasi setiap hit kinerja di mana model tradisionalfork()
/pthread_create()
-on-permintaan harus membuat dan menghancurkan utas. Dan, seperti yang disebutkan dalam postscript a), ini juga mengamortisasi masalah ruang stack. Anda mungkin dapat melayani ribuan permintaan dengan, katakanlah, 16 utas baik-baik saja.Ryan menggunakan utas untuk bagian-bagian yang memblokir (Sebagian besar node.js menggunakan non-blocking IO) karena beberapa bagian gila sulit untuk menulis yang tidak memblokir. Tapi saya yakin keinginan Ryan adalah agar semuanya tidak menghalangi. Pada slide 63 (desain internal) Anda melihat Ryan menggunakan libev (perpustakaan yang mengabstraksi pemberitahuan kejadian asinkron) untuk penguncian acara yang tidak memblokir . Karena event-loop node.js membutuhkan thread yang lebih kecil yang mengurangi switching konteks, konsumsi memori dll.
sumber
Utas hanya digunakan untuk menangani fungsi yang tidak memiliki fasilitas asinkron, seperti
stat()
.The
stat()
Fungsi selalu menghalangi, sehingga Node.js kebutuhan untuk menggunakan thread untuk melakukan panggilan sebenarnya tanpa menghalangi thread utama (event loop). Secara potensial, tidak ada utas dari kumpulan utas yang akan pernah digunakan jika Anda tidak perlu memanggil fungsi semacam itu.sumber
Saya tidak tahu apa-apa tentang cara kerja internal node.js, tapi saya bisa melihat bagaimana menggunakan sebuah event loop dapat mengungguli penanganan I / O berulir. Bayangkan permintaan disk, beri saya staticFile.x, buatlah 100 permintaan untuk file itu. Setiap permintaan biasanya membutuhkan utas untuk mengambil kembali file itu, yaitu 100 utas.
Sekarang bayangkan permintaan pertama membuat satu utas yang menjadi objek penerbit, ke-99 permintaan lainnya pertama-tama melihat apakah ada objek penerbit untuk staticFile.x, jika demikian, dengarkan ketika sedang melakukan pekerjaannya, jika tidak, mulailah utas baru dan dengan demikian objek penerbit baru.
Setelah utas tunggal selesai, ia meneruskan staticFile.x ke 100 pendengar dan menghancurkan dirinya sendiri, sehingga permintaan berikutnya membuat utas baru dan objek penerbit.
Jadi 100 thread vs 1 thread dalam contoh di atas, tetapi juga 1 disk pencarian, bukan 100 disk pencarian, keuntungannya bisa sangat phenominal. Ryan adalah pria yang cerdas!
Cara lain untuk melihatnya adalah salah satu contohnya di awal film. Dari pada:
Sekali lagi, 100 pertanyaan yang terpisah untuk database versus ...:
Jika kueri sudah berjalan, kueri yang sama lainnya hanya akan ikut-ikutan, sehingga Anda dapat memiliki 100 kueri dalam satu perjalanan pulang-pergi database tunggal.
sumber