
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 falsetimeout 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 truestdio ignoreMisalnya 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.fork8000 ).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.setupPrimaryKami 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.