Pertanyaan Kapan kolam thread digunakan?


Jadi saya memiliki pemahaman tentang cara kerja Node.js: ia memiliki satu siklus pendengar yang menerima peristiwa dan kemudian mendelegasikannya ke kumpulan pekerja. Benang pekerja memberi tahu pendengar setelah selesai bekerja, dan pendengar kemudian mengembalikan respons ke pemanggil.

Pertanyaan saya adalah ini: jika saya berdiri di server HTTP di Node.js dan memanggil tidur pada salah satu acara jalur yang saya lewati (seperti "/ test / sleep"), seluruh sistem terhenti. Bahkan benang pendengar tunggal. Tetapi pemahaman saya adalah bahwa kode ini terjadi di kolam pekerja.

Sekarang, sebaliknya, ketika saya menggunakan Mongoose untuk berbicara dengan MongoDB, DB membaca adalah operasi I / O yang mahal. Node tampaknya dapat mendelegasikan pekerjaan ke utas dan menerima panggilan balik ketika selesai; waktu yang diambil untuk memuat dari DB tampaknya tidak memblokir sistem.

Bagaimana cara Node.js memutuskan untuk menggunakan utas thread ulir vs utas pendengar? Mengapa saya tidak bisa menulis kode acara yang tidur dan hanya memblokir utas thread utas?


75
2018-03-25 19:20


asal


Jawaban:


Pemahaman Anda tentang cara kerja node tidak benar ... tetapi ini adalah kesalahpahaman yang umum, karena realitas situasinya sebenarnya cukup rumit, dan biasanya dirangkai menjadi frase kecil yang bernas seperti "simpul berulir tunggal" yang terlalu menyederhanakan hal-hal .

Untuk saat ini, kami akan mengabaikan multi-processing / multi-threading eksplisit melalui gugus dan benang webworker, dan hanya berbicara tentang simpul non-berulir khas.

Node berjalan dalam satu lingkaran peristiwa. Ini berulir tunggal, dan Anda hanya mendapatkan satu utas itu. Semua javascript yang Anda tulis mengeksekusi di loop ini, dan jika operasi pemblokiran terjadi dalam kode itu, maka itu akan memblokir seluruh loop dan tidak ada yang lain yang akan terjadi sampai selesai. Ini adalah sifat simpul berulir tunggal yang sering Anda dengar. Tapi, itu bukan gambaran keseluruhan.

Fungsi dan modul tertentu, biasanya ditulis dalam C / C ++, mendukung I / O asinkron. Saat Anda memanggil fungsi dan metode ini, mereka secara internal mengatur meneruskan panggilan ke utas pekerja. Misalnya, saat Anda menggunakan fs modul untuk meminta file, file fs melewati modul yang memanggil ke pekerja thread, dan pekerja yang menunggu tanggapannya, yang kemudian menyajikan kembali ke loop acara yang telah berputar tanpa itu untuk sementara. Semua ini dijauhkan dari Anda, pengembang node, dan beberapa diabstraksikan menjauh dari pengembang modul melalui penggunaan libuv.

Seperti yang ditunjukkan oleh Denis Dollfus di komentar (dari jawaban ini untuk pertanyaan serupa), strategi yang digunakan oleh libuv untuk mencapai I / O asynchronous tidak selalu merupakan kumpulan thread, khususnya dalam kasus http modul strategi yang berbeda tampaknya digunakan saat ini. Untuk tujuan kami di sini, terutama penting untuk dicatat bagaimana konteks asynchronous dicapai (dengan menggunakan libuv) dan bahwa kolam thread yang dikelola oleh libuv adalah salah satu dari beberapa strategi yang ditawarkan oleh perpustakaan itu untuk mencapai asynchronicity.


Pada sebagian besar terkait tangen, ada analisis yang jauh lebih mendalam tentang bagaimana node mencapai asynchronicity, dan beberapa potensi masalah terkait dan bagaimana mengatasinya, dalam artikel yang sangat bagus ini. Sebagian besar mengembang pada apa yang saya tulis di atas, tetapi juga menunjukkan:

  • Modul eksternal apa pun yang Anda sertakan dalam proyek Anda yang memanfaatkan native C ++ dan libuv kemungkinan akan menggunakan pool thread (pikirkan: akses database)
  • libuv memiliki ukuran pool thread default dari 4, dan menggunakan antrian untuk mengelola akses ke thread pool - hasilnya adalah jika Anda memiliki 5 query DB yang berjalan lama semua terjadi pada saat yang sama, salah satu dari mereka (dan lainnya asynchronous tindakan yang bergantung pada rangkaian ulir) akan menunggu permintaan tersebut selesai bahkan sebelum memulai
  • Anda dapat mengurangi ini dengan meningkatkan ukuran kolam thread melalui UV_THREADPOOL_SIZE variabel lingkungan, selama Anda melakukannya sebelum rangkaian utas diperlukan dan dibuat: process.env.UV_THREADPOOL_SIZE = 10;

Jika Anda ingin multi-processing tradisional atau multi-threading di node, Anda bisa mendapatkannya melalui built in cluster modul atau berbagai modul lain seperti yang disebutkan sebelumnya webworker-threads, atau Anda dapat memalsukannya dengan menerapkan beberapa cara memecah pekerjaan Anda dan menggunakan secara manual setTimeout atau setImmediate atau process.nextTick untuk menghentikan sementara pekerjaan Anda dan melanjutkannya di putaran berikutnya agar proses lainnya selesai (tetapi itu tidak disarankan).

Harap dicatat, jika Anda menulis kode menjalankan / memblokir lama di javascript, Anda mungkin membuat kesalahan. Bahasa lain akan tampil jauh lebih efisien.


171
2018-03-25 19:44



Jadi saya memiliki pemahaman tentang cara kerja Node.js: ia memiliki satu siklus pendengar yang menerima peristiwa dan kemudian mendelegasikannya ke kumpulan pekerja. Benang pekerja memberi tahu pendengar setelah selesai bekerja, dan pendengar kemudian mengembalikan respons ke pemanggil.

Ini tidak benar-benar akurat. Node.js hanya memiliki satu "pekerja" thread yang melakukan eksekusi javascript. Ada benang dalam simpul yang menangani pemrosesan IO, tetapi menganggapnya sebagai "pekerja" adalah kesalahpahaman. Hanya ada penanganan IO dan beberapa rincian lain dari implementasi internal node, tetapi sebagai programmer Anda tidak dapat mempengaruhi perilaku mereka selain beberapa parameter misc seperti MAX_LISTENERS.

Pertanyaan saya adalah ini: jika saya berdiri di server HTTP di Node.js dan memanggil tidur pada salah satu acara jalur yang saya lewati (seperti "/ test / sleep"), seluruh sistem terhenti. Bahkan benang pendengar tunggal. Tetapi pemahaman saya adalah bahwa kode ini terjadi di kolam pekerja.

Tidak ada mekanisme tidur di JavaScript. Kita bisa mendiskusikan ini lebih konkret jika Anda memposting cuplikan kode dari apa yang Anda pikir "tidur" artinya. Tidak ada fungsi seperti itu untuk memanggil untuk mensimulasikan sesuatu seperti time.sleep(30) di python, misalnya. Ada setTimeout tapi itu pada dasarnya tidak tidur. setTimeout dan setInterval secara eksplisit melepaskan, tidak memblokir, perulangan peristiwa sehingga bit kode lain dapat dijalankan pada utas eksekusi utama. Satu-satunya hal yang dapat Anda lakukan adalah menyibukkan CPU dengan perhitungan di memori, yang akan benar-benar mematikan thread eksekusi utama dan membuat program Anda tidak responsif.

Bagaimana cara Node.js memutuskan untuk menggunakan utas thread ulir vs utas pendengar? Mengapa saya tidak bisa menulis kode acara yang tidur dan hanya memblokir utas thread utas?

Jaringan IO selalu asynchronous. Akhir dari cerita. Disk IO memiliki API sinkron dan asynchronous, jadi tidak ada "keputusan". node.js akan berperilaku sesuai dengan fungsi inti API yang Anda sebut sync vs async normal. Sebagai contoh: fs.readFile vs fs.readFileSync. Untuk proses anak, ada juga yang terpisah child_process.exec dan child_process.execSync Lebah.

Rule of thumb selalu menggunakan API asynchronous. Alasan sah untuk menggunakan API sinkronisasi adalah untuk kode inisialisasi dalam layanan jaringan sebelum mendengarkan koneksi atau dalam skrip sederhana yang tidak menerima permintaan jaringan untuk membuat alat dan hal semacam itu.


14
2018-03-25 19:38



Kesalahpahaman ini hanyalah perbedaan antara pre-emptive multi-tasking dan kerjasama multitasking ...

Tidur mematikan seluruh karnaval karena benar-benar ada satu garis untuk semua wahana, dan Anda menutup gerbang. Anggap saja sebagai "penerjemah JS dan beberapa hal lain" dan abaikan benang ... untuk Anda, hanya ada satu utas, ...

... jadi jangan menghalangi.


0
2018-04-02 22:56