Kita semua tahu bahwa Node.js menggunakan model I/O asinkron berbasis peristiwa berulir tunggal. Karakteristiknya menentukan bahwa Node.js tidak dapat memanfaatkan CPU multi-inti dan tidak pandai menyelesaikan beberapa operasi non-I/O ( seperti mengeksekusi skrip), komputasi AI, pemrosesan gambar, dll.), untuk mengatasi masalah tersebut, Node.js menyediakan solusi multi-proses (utas) konvensional (untuk diskusi tentang proses dan utas, silakan merujuk ke penulisnya. artikel lainnya Node.js dan Model Konkurensi), artikel ini akan memperkenalkan Anda pada mekanisme multi-thread Node.js.
Kita dapat menggunakan modul child_process
untuk membuat proses anak Node.js untuk menyelesaikan beberapa tugas khusus (seperti menjalankan skrip). Modul ini terutama menyediakan exec
, execFile
, fork
, spwan
dan metode lainnya . menggunakan.
const { exec } = memerlukan('proses_anak'); exec('ls -al', (kesalahan, stdout, stderr) => { konsol.log(stdout); });
Metode ini memproses string perintah sesuai dengan file yang dapat dieksekusi yang ditentukan oleh options.shell
, menyimpan outputnya selama eksekusi perintah, dan kemudian mengembalikan hasil eksekusi dalam bentuk parameter fungsi panggilan balik hingga eksekusi perintah selesai.
Parameter dari metode ini dijelaskan sebagai berikut:
command
: perintah yang akan dieksekusi (seperti ls -al
);
options
: pengaturan parameter (opsional), properti yang relevan adalah sebagai berikut:
cwd
: direktori kerja saat ini dari proses anak , defaultnya adalah nilai process.cwd()
;
env
: pengaturan variabel lingkungan (objek pasangan nilai kunci), nilai defaultnya process.env
shell
encoding
utf8
/
Unix
/bin/sh
nilai default di Windows
adalah nilai process.env.ComSpec
(jika kosong adalah cmd.exe
);
} = memerlukan('proses_anak'); exec("print('Halo Dunia!')", { shell: 'python' }, (kesalahan, stdout, stderr) => { konsol.log(stdout); });
Menjalankan contoh di atas akan menampilkan Hello World!
yang setara dengan subproses yang mengeksekusi perintah python -c "print('Hello World!')"
Oleh karena itu, saat menggunakan atribut ini, Anda perlu memperhatikan file eksekusi tertentu. Eksekusi pernyataan terkait melalui opsi -c
harus didukung.
Catatan: Kebetulan Node.js
juga mendukung opsi -c
, tetapi ini setara dengan opsi --check
. Ini hanya digunakan untuk mendeteksi apakah ada kesalahan sintaksis dalam skrip yang ditentukan dan tidak akan menjalankan skrip yang relevan.
signal
: Gunakan AbortSignal yang ditentukan untuk menghentikan proses anak. Atribut ini tersedia di atas v14.17.0, misalnya:
const { exec } = require('child_process'); const ac = baru AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
Pada contoh di atas, kita bisa menghentikan proses anak lebih awal dengan memanggil ac.abort()
.
timeout
: Batas waktu proses anak (jika nilai atribut ini lebih besar dari 0
, maka ketika waktu berjalan proses anak melebihi nilai yang ditentukan, sinyal penghentian yang ditentukan oleh atribut killSignal
akan dikirim ke proses anak ), dalam milimeter, nilai defaultnya adalah 0
;
killSignal
maxBuffer
dan output 1024 * 1024
pun akan terpotong
: Sinyal penghentian proses anak, nilai defaultnya adalah SIGTERM
;
uid
: uid
untuk menjalankan proses anak;
gid
: gid
untuk menjalankan proses anak;
windowsHide
: apakah akan menyembunyikan jendela konsol dari proses anak, yang biasa digunakan dalam sistem Windows
, nilai defaultnya adalah false
;
callback
: fungsi panggilan balik, termasuk error
, stdout
, stderr
Parameter:
error
: Jika baris perintah berhasil dijalankan, nilainya adalah null
, jika tidak, nilainya adalah turunan dari Error, di mana error.code
adalah pintu keluarnya kode kesalahan dari proses anak, error.signal
adalah sinyal untuk penghentian proses anak;buffer
stdout
stderr
: encoding
stdout
dan stderr
dari proses dikodekan sesuai dengan nilai atribut encoding
, atau nilai stdout
atau stderr
adalah string yang tidak dapat dikenali, maka akan dikodekan sesuai dengan buffer
.const { execFile } = memerlukan('child_process'); execFile('ls', ['-al'], (kesalahan, stdout, stderr) => { konsol.log(stdout); });
Fungsi metode ini mirip dengan exec
. Satu-satunya perbedaan adalah execFile
secara langsung memproses perintah dengan file executable yang ditentukan (yaitu, nilai parameter file
) secara default, yang membuat efisiensinya sedikit lebih tinggi daripada exec
(jika Anda melihat logika pemrosesan shell, saya merasa efisiensinya dapat diabaikan).
Parameter dari metode ini dijelaskan sebagai berikut:
file
: nama atau jalur file yang dapat dieksekusi;
args
: daftar parameter dari file yang dapat dieksekusi
options
: pengaturan parameter (tidak dapat ditentukan), properti yang relevan adalah sebagai berikut:
shell
: ketika nilainya false
itu berarti langsung menggunakan yang ditentukan. File yang dapat dieksekusi (yaitu, nilai parameter file
) memproses perintah. Ketika nilainya true
atau string lain, fungsinya setara dengan shell
di exec
. Nilai defaultnya adalah false
;windowsVerbatimArguments
: apakah akan mengutip atau keluar dari parameter di Windows
, Atribut ini akan Unix
false
timeout
maxBuffer
cwd
gid
uid
encoding
killSignal
env
, windowsHide
, dan signal
telah diperkenalkan di atas dan tidak akan diulangi di sini.callback
: fungsi panggilan balik, yang setara dengan callback
di exec
dan tidak akan dijelaskan di sini.
const { fork } = memerlukan('child_process'); const echo = garpu('./echo.js', { diam: benar }); echo.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); echo.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); echo.on('close', (kode) => { console.log(`proses anak keluar dengan kode ${code}`); });
Metode ini digunakan untuk membuat instance Node.js baru untuk mengeksekusi skrip Node.js yang ditentukan dan berkomunikasi dengan proses induk melalui IPC.
Parameter metode ini dijelaskan sebagai berikut:
modulePath
: jalur skrip Node.js yang akan dijalankan;
args
: daftar parameter yang diteruskan ke skrip Node.js
options
: pengaturan parameter (tidak dapat ditentukan), atribut terkait seperti:
detached
: lihat di bawah untuk spwan
Deskripsi options.detached
;
execPath
: Membuat file yang dapat dieksekusi dari proses anak;
execArgv
: Daftar parameter string diteruskan ke file yang dapat dieksekusi, nilai defaultnya adalah process.execArgv
serialization
: Jenis nomor seri pesan antar-proses, nilai yang tersedia adalah json
dan advanced
, nilai defaultnya adalah json
;
slient
: Jika true
, stdin
, stdout
dan stderr
dari proses anak akan diteruskan ke proses induk
spwan
options.stdio
stdio
stdin
stdout
stderr
dari proses induk akan diwarisi; nilai defaultnya adalah false
;
Yang perlu diperhatikan di sini adalah
slient
akan diabaikan;ipc
harus disertakan (seperti [0, 1, 2, 'ipc']
), jika tidak maka akan terjadi an pengecualian akan dilempar.Properti cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
dan killSignal
telah diperkenalkan di atas dan tidak akan diulangi di sini.
const { spawn } = memerlukan('proses_anak'); const ls = spawn('ls', ['-al']); ls.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); ls.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); ls.on('close', (kode) => { console.log(`proses anak keluar dengan kode ${code}`); });
Metode ini adalah metode dasar modul child_process
. exec
, execFile
, dan fork
pada akhirnya akan memanggil spawn
untuk membuat proses anak.
Parameter dari metode ini dijelaskan sebagai berikut:
command
: nama atau jalur file yang dapat dieksekusi;
args
: daftar parameter yang diteruskan ke file yang dapat dieksekusi
options
: pengaturan parameter (tidak dapat ditentukan), atribut yang relevan adalah sebagai berikut:
argv0
: dikirim ke nilai argv[0 ] proses anak, nilai default adalah nilai parameter command
;
detached
: apakah akan mengizinkan proses anak berjalan secara independen dari proses induk (yaitu, setelah proses induk keluar, anak proses dapat terus berjalan), nilai defaultnya adalah false
, dan ketika nilainya true
, setiap platform Efeknya adalah sebagai berikut:
Windows
, setelah proses induk keluar, proses anak dapat terus berjalan, dan proses anak memiliki jendela konsolnya sendiri (setelah fitur ini dimulai, fitur ini tidak dapat diubah selama proses berjalan);Windows
, proses anak akan berfungsi sebagai pemimpin grup sesi proses baru apakah proses anak dipisahkan dari proses induk, proses anak dapat terus berjalan setelah proses induk keluar.Perlu dicatat bahwa jika proses anak perlu melakukan tugas jangka panjang dan ingin proses induk keluar lebih awal, poin-poin berikut harus dipenuhi pada saat yang sama:
unref
dari proses anak untuk menghapus anak tersebut proses dari loop acara dari proses induk;detached
Set true
stdio
ignore
Misalnya contoh berikut:
// hello.js const fs = memerlukan('fs'); biarkan indeks = 0; fungsi dijalankan() { setWaktu habis(() => { fs.writeFileSync('./hello', `index: ${index}`); jika (indeks < 10) { indeks += 1; berlari(); } }, 1000); } berlari(); // main.js const { spawn } = memerlukan('child_process'); const anak = spawn('simpul', ['./hello.js'], { terpisah: benar, stdio: 'abaikan' }); child.unref();
stdio
: konfigurasi input dan output standar proses anak, nilai defaultnya adalah pipe
, nilainya adalah string atau array:
pipe
diubah menjadi ['pipe', 'pipe', 'pipe']
), nilai yang tersedia adalah pipe
, overlapped
, ignore
, inherit
;stdin
, stdout
dan stderr
masing-masing, masing-masing Nilai item yang tersedia adalah pipe
, overlapped
, ignore
, inherit
, ipc
, Objek aliran, bilangan bulat positif (deskriptor file dibuka dalam proses induk), null
(jika ya terletak di tiga item pertama array, itu setara dengan pipe
, jika tidak maka setara dengan ignore
), undefined
(jika terletak di tiga item pertama array, itu setara dengan pipe
, jika tidak maka setara dengan ignore
).Atribut cwd
, env
, uid
, gid
, serialization
, shell
(nilai boolean
atau string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
telah diperkenalkan di atas dan tidak akan diulangi di sini.
Di atas memberikan pengenalan singkat tentang penggunaan metode utama dalam modul child_process
Karena metode execSync
, execFileSync
, forkSync
, dan spwanSync
adalah versi sinkron dari exec
, execFile
, dan spwan
, tidak ada perbedaan dalam parameternya, jadi hal itu tidak akan terulang kembali.
Melalui modul cluster
, kita dapat membuat cluster proses Node.js. Dengan menambahkan proses Node.js ke dalam cluster, kita dapat memanfaatkan keunggulan multi-core dan mendistribusikan tugas program ke berbagai proses untuk meningkatkan eksekusi. efisiensi program; di bawah ini, kita akan menggunakan Contoh ini memperkenalkan penggunaan modul cluster
:
const http = require('http'); const cluster = memerlukan('cluster'); const numCPUs = memerlukan('os').cpus().panjang; if (cluster.isPrimary) { untuk (biarkan i = 0; i < jumlahCPU; i++) { cluster.fork(); } } kalau tidak { http.createServer((permintaan, res) => { res.writeHead(200); res.end(`${proses.pid}n`); }).mendengarkan(8000); }
Contoh di atas dibagi menjadi dua bagian berdasarkan penilaian atribut cluster.isPrimary
(yaitu, menilai apakah proses saat ini adalah proses utama):
cluster.fork
8000
).Jalankan contoh di atas dan akses http://localhost:8000/
di browser. Kita akan menemukan bahwa pid
yang dikembalikan berbeda untuk setiap akses, yang menunjukkan bahwa permintaan memang didistribusikan ke setiap proses anak. Strategi penyeimbangan beban default yang diadopsi oleh Node.js adalah penjadwalan round-robin, yang dapat dimodifikasi melalui variabel lingkungan NODE_CLUSTER_SCHED_POLICY
atau properti cluster.schedulingPolicy
:
NODE_CLUSTER_SCHED_POLICY = rr // atau tidak ada cluster.schedulingPolicy = cluster.SCHED_RR; // atau cluster.SCHED_NONE.
Hal lain yang perlu diperhatikan adalah meskipun setiap proses anak telah membuat server HTTP dan mendengarkan port yang sama, bukan berarti proses anak ini bebas untuk bersaing. permintaan pengguna. , karena ini tidak dapat menjamin bahwa beban semua proses anak seimbang. Oleh karena itu, proses yang benar adalah proses utama mendengarkan port, dan kemudian meneruskan permintaan pengguna ke sub-proses tertentu untuk diproses sesuai dengan kebijakan distribusi.
Karena proses terisolasi satu sama lain, proses umumnya berkomunikasi melalui mekanisme seperti memori bersama, penyampaian pesan, dan pipa. Node.js menyelesaikan komunikasi antara proses induk dan anak melalui消息传递
, seperti contoh berikut:
const http = require('http'); const cluster = memerlukan('cluster'); const numCPUs = memerlukan('os').cpus().panjang; if (cluster.isPrimary) { untuk (biarkan i = 0; i < jumlahCPU; i++) { const pekerja = cluster.fork(); pekerja.on('pesan', (pesan) => { console.log(`Saya utama(${proses.pid}), saya mendapat pesan dari pekerja: "${message}"`); pekerja.kirim(`Kirim pesan ke pekerja`) }); } } kalau tidak { proses.pada('pesan', (pesan) => { console.log(`Saya pekerja(${proses.pid}), saya mendapat pesan dari utama: "${message}"`) }); http.createServer((permintaan, res) => { res.writeHead(200); res.end(`${proses.pid}n`); process.send('Kirim pesan ke utama'); }).mendengarkan(8000); }
Jalankan contoh di atas dan kunjungi http://localhost:8000/
, lalu periksa terminal, kita akan melihat output seperti berikut:
Saya primer (44460), saya mendapat pesan dari pekerja: "Kirim pesan ke primer" Saya pekerja (44461), saya mendapat pesan dari utama: "Kirim pesan ke pekerja" Saya utama (44460), saya mendapat pesan dari pekerja: "Kirim pesan ke utama" Saya pekerja (44462), saya mendapat pesan dari utama: "Kirim pesan ke pekerja"
Dengan menggunakan mekanisme ini, kita dapat memantau status setiap proses anak sehingga ketika terjadi kecelakaan pada proses anak, kita dapat melakukan intervensi tepat waktu. untuk memastikan Ketersediaan Layanan.
Antarmuka modul cluster
sangat sederhana. Untuk menghemat ruang, di sini kami hanya membuat beberapa pernyataan khusus tentang metode cluster.setupPrimary
. Untuk metode lain, silakan periksa dokumentasi resmi:
cluster.setupPrimary
dipanggil, pengaturan yang relevan akan disinkronkan ke atribut cluster.settings
, dan setiap setiap panggilan didasarkan pada nilai atribut cluster.settings
saat ini;cluster.setupPrimary
dipanggil, ini tidak berdampak pada proses anak yang sedang berjalan, hanya panggilan cluster.fork
berikutnya terpengaruh;cluster.setupPrimary
dipanggil, itu tidak mempengaruhi penerusan selanjutnya ke cluster.fork
Parameter env
dari panggilancluster.setupPrimary
Kami memperkenalkan modul cluster
sebelumnya, yang melaluinya kita dapat membuat cluster proses Node.js untuk meningkatkan efisiensi menjalankan program. Namun, cluster
didasarkan pada model multi-proses, dengan peralihan antara proses dan isolasi yang berbiaya tinggi sumber daya antar proses. Peningkatan jumlah proses anak dapat dengan mudah menyebabkan masalah tidak dapat merespons karena keterbatasan sumber daya sistem. Untuk mengatasi masalah tersebut, Node.js worker_threads
. Di bawah ini kami memperkenalkan secara singkat penggunaan modul ini melalui contoh spesifik:
// server.js const http = memerlukan('http'); const { Pekerja } = memerlukan('pekerja_threads'); http.createServer((permintaan, res) => { const httpWorker = Pekerja baru('./http_worker.js'); httpWorker.on('pesan', (hasil) => { res.writeHead(200); res.end(`${hasil}n`); }); httpWorker.postMessage('Tom'); }).mendengarkan(8000); // http_worker.js const { parentPort } = memerlukan('pekerja_threads'); parentPort.on('pesan', (nama) => { parentPort.postMessage(`Selamat datang ${nama}!`); });
Contoh di atas menunjukkan penggunaan sederhana worker_threads
. Saat worker_threads
, Anda perlu memperhatikan poin-poin berikut:
Buat instance Worker worker_threads.Worker
, di mana skrip Worker dapat berupa file JavaScript
independen atau字符串
, misalnya, contoh di atas dapat dimodifikasi sebagai:
const code = "const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${ nama}!` );})"; const httpWorker = new Worker(code, { eval: true });
Saat membuat instance Worker worker_threads.Worker
, Anda dapat mengatur metadata awal sub-thread Worker dengan menentukan workerData
, seperti:
// server .js const { Pekerja } = memerlukan('pekerja_threads'); const httpWorker = Pekerja baru('./http_worker.js', { data pekerja: { nama: 'Tom'} }); // http_worker.js const { data pekerja } = memerlukan('utas_pekerja'); console.log(workerData);
Saat membuat instance Worker worker_threads.Worker
, Anda dapat mengatur SHARE_ENV
untuk menyadari kebutuhan untuk berbagi variabel lingkungan antara sub-thread Worker dan thread utama, misalnya:
const { Worker, SHARE_ENV } = membutuhkan('utas_pekerja'); const pekerja = Pekerja baru('proses.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV }); pekerja.pada('keluar', () => { konsol.log(proses.env.SET_IN_WORKER); });
Berbeda dengan mekanisme komunikasi antar-proses di cluster
, worker_threads
menggunakan MessageChannel untuk berkomunikasi antar thread:
parentPort.postMessage
, dan memproses pesan dari thread utama dengan mendengarkan ke message
utama. peristiwa message
dari pesan parentPort
;httpWorker
melalui metode postMessage
dari instance sub-thread Worker (di sini adalah httpWorker
, dan digantikan oleh sub-thread Worker di bawah), dan memproses pesan dari sub-thread Worker; dengan mendengarkan acara message
httpWorker
.Di Node.js, apakah itu proses anak yang dibuat oleh cluster
atau thread anak Worker yang dibuat worker_threads
, semuanya memiliki instance V8 dan loop peristiwanya sendiri. Perbedaannya adalah
Meskipun tampaknya sub-thread Worker lebih efisien daripada proses anak, sub-thread Worker juga memiliki kekurangan, yaitu cluster
menyediakan penyeimbangan beban, sedangkan worker_threads
mengharuskan kita menyelesaikan sendiri desain dan implementasi penyeimbangan beban.
Artikel ini memperkenalkan penggunaan tiga modul child_process
, cluster
worker_threads
di Node.js. Melalui ketiga modul ini, kita dapat memanfaatkan sepenuhnya keunggulan CPU multi-core dan secara efisien menyelesaikan beberapa masalah khusus dalam multi-thread ( mode thread). Efisiensi pengoperasian tugas (seperti AI, pemrosesan gambar, dll.). Setiap modul memiliki skenario yang dapat diterapkan. Artikel ini hanya menjelaskan penggunaan dasarnya. Cara menggunakannya secara efisien berdasarkan masalah Anda sendiri masih perlu dieksplorasi sendiri. Terakhir, jika ada kesalahan pada artikel ini, saya harap Anda dapat memperbaikinya. Saya berharap Anda semua senang coding setiap hari.