Mencipta Utas Pekerja dan Menghantar Mesej
Cipta pekerja, tukar data melalui postMessage dan gunakan workerData serta MessageChannel untuk komunikasi berstruktur.
Mencipta Utas Pekerja dan Menghantar Mesej ialah pelajaran Kem Intensif Pembangunan Backend Node.js percuma di CoddyKit. Ini ialah pelajaran 2 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Backend Node.js, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Backend Node.js merangkumi sejumlah 4 pelajaran.
Mengapa Worker Threads Wujud
Node.js menjalankan JavaScript anda pada satu bebenang. Ini sangat baik untuk kerja yang terikat pada I/O (HTTP, pangkalan data, fail) kerana gelung peristiwa tidak pernah tersekat. Namun, tugas yang terikat pada CPU — mengubah saiz imej, pencincangan, menghuraikan muatan besar, kriptografi — berjalan secara segerak dan membekukan seluruh pelayan sehingga selesai.
- Terikat pada I/O → kekal pada gelung peristiwa, gunakan API tak segerak.
- Terikat pada CPU → serahkan kepada bebenang
Workersupaya bebenang utama terus melayani permintaan.
Modul worker_threads membolehkan anda menjalankan JavaScript secara selari pada bebenang OS berasingan, setiap satunya dengan pengasing V8 dan gelung peristiwa sendiri.
Mencipta Worker Pertama Anda
Anda mencipta bebenang dengan new Worker(filename). Fail itu berjalan pada bebenang baharu. Gunakan isMainThread supaya satu fail boleh berkelakuan berbeza bergantung pada tempat ia berjalan.
Di bawah, bebenang utama mencipta worker yang merujuk kembali kepada fail yang sama. Cabang worker menjalankan kerja tersebut dan keluar.
const { Worker, isMainThread } = require('node:worker_threads');
if (isMainThread) {
console.log('main: spawning worker');
const worker = new Worker(__filename);
worker.on('exit', (code) => console.log('worker exited with', code));
} else {
// This branch runs inside the worker thread
let sum = 0;
for (let i = 0; i < 1e7; i++) sum += i;
console.log('worker computed sum =', sum);
}Menghantar Data Masuk dengan workerData
Untuk memberikan input kepada worker semasa penciptaan, hantarkan workerData dalam objek pilihan. Di dalam worker, bacanya daripada modul worker_threads.
workerDatadiklonkan (klon berstruktur), bukan dikongsi — mengubahnya dalam worker tidak menjejaskan bebenang utama.- Gunakannya untuk parameter tugas awal: laluan fail, julat nombor atau konfigurasi.
const { Worker, isMainThread, workerData } = require('node:worker_threads');
if (isMainThread) {
new Worker(__filename, { workerData: { start: 1, end: 5 } });
} else {
const { start, end } = workerData;
let product = 1;
for (let i = start; i <= end; i++) product *= i;
console.log('factorial-ish product =', product);
}Mendapatkan Hasil Kembali dengan postMessage
workerData adalah sehala dan hanya sekali. Untuk mendapatkan hasil, gunakan saluran mesej terbina dalam yang dimiliki oleh setiap worker:
- Di dalam worker:
parentPort.postMessage(value)menghantar data kepada induk. - Pada bebenang utama:
worker.on('message', cb)menerimanya.
Mesej adalah tak segerak dan diklonkan secara berstruktur, jadi bebenang utama kekal responsif semasa worker membuat pengiraan.
const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
if (isMainThread) {
const worker = new Worker(__filename, { workerData: 40 });
worker.on('message', (result) => {
console.log('main received:', result);
});
} else {
function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
parentPort.postMessage(fib(workerData));
}Pemesejan Dua Hala
Saluran ini berfungsi dalam kedua-dua arah. Induk boleh menggunakan worker.postMessage() dan worker mendengar dengan parentPort.on('message', ...). Ini menjadikan worker sebagai perkhidmatan jangka panjang yang memproses banyak tugas, bukannya satu sahaja.
Perhatikan bahawa worker kekal hidup sambil mendengar mesej; anda mesti memberitahunya secara jelas untuk berhenti (di sini, penanda null mencetuskan process.exit).
const { Worker, isMainThread, parentPort } = require('node:worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (r) => console.log('squared ->', r));
[2, 3, 4].forEach((n) => worker.postMessage(n));
worker.postMessage(null); // sentinel to stop
} else {
parentPort.on('message', (n) => {
if (n === null) { process.exit(0); }
parentPort.postMessage(n * n);
});
}Menjadikan Satu Tugas sebagai Promise
Dalam kod backend, anda biasanya mahukan fungsi tak segerak yang kemas: panggil fungsi itu dan gunakan await untuk mendapatkan hasil. Balut kitar hayat worker dalam Promise, selesaikan apabila menerima message dan tolak apabila berlaku error atau exit bukan sifar.
Corak ini ialah asas bagi mana-mana kumpulan worker: satu janji untuk setiap tugas.
const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
function runJob(payload) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData: payload });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error('exit code ' + code));
});
});
}
if (isMainThread) {
runJob([5, 10, 15]).then((sum) => console.log('total =', sum));
} else {
const total = workerData.reduce((a, b) => a + b, 0);
parentPort.postMessage(total);
}Klon Berstruktur: Perkara yang Boleh Anda Hantar
Mesej disalin menggunakan algoritma klon berstruktur, bukan JSON.stringify. Ini bermakna anda boleh menghantar lebih daripada JSON biasa.
- Disokong: objek, tatasusunan,
Map,Set,Date,RegExp,ArrayBuffer, tatasusunan ditaip danBigInt. - TIDAK disokong: fungsi, tika kelas dengan kaedah, objek seperti DOM dan apa-apa yang mengandungi penutupan — semua ini menghasilkan
DataCloneError.
Mengklon objek besar memerlukan CPU dan memori. Untuk penimbal binari besar, lebih baik memindahkan daripada menyalin (dalam bab seterusnya).
Memindahkan Penimbal dan Bukannya Menyalin
Untuk data binari yang besar, menyalin adalah membazir. Hantarkan senarai pemindahan sebagai argumen kedua kepada postMessage. Pemilikan ArrayBuffer berpindah ke bebenang lain — tanpa penyalinan — dan penimbal menjadi tidak boleh digunakan pada pihak penghantar (nilai byteLength menjadi 0).
Gunakan kaedah ini untuk bait imej, bahagian fail atau apa-apa muatan bersaiz megabait bagi mengelakkan penduaan memori.
const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
if (isMainThread) {
const buf = new Uint8Array([1, 2, 3, 4]).buffer;
const worker = new Worker(__filename, { workerData: buf }, { transferList: [buf] });
worker.on('message', (sum) => console.log('byte sum =', sum));
} else {
const bytes = new Uint8Array(workerData);
let sum = 0;
for (const b of bytes) sum += b;
parentPort.postMessage(sum);
}MessageChannel untuk Saluran Sampingan
Selain parentPort terbina dalam, anda boleh mencipta saluran sendiri dengan new MessageChannel(). Ia memberikan dua port terpaut, port1 dan port2. Simpan satu, pindahkan satu lagi ke dalam worker, dan kini anda mempunyai saluran khusus — berguna untuk memisahkan mesej kawalan daripada data atau untuk komunikasi antara worker.
MessagePort sendiri boleh dipindahkan, jadi anda boleh menghantarnya di dalam workerData atau mesej dengan senarai pemindahan.
const { Worker, isMainThread, MessageChannel, parentPort, workerData } =
require('node:worker_threads');
if (isMainThread) {
const { port1, port2 } = new MessageChannel();
new Worker(__filename, { workerData: { port: port2 }, transferList: [port2] });
port1.on('message', (msg) => {
console.log('main got on side channel:', msg);
port1.close();
});
port1.postMessage('ping');
} else {
const { port } = workerData;
port.on('message', (msg) => {
port.postMessage('pong (you said: ' + msg + ')');
});
}Mengendalikan Ralat dan Penutupan Bersih
Worker boleh ranap. Sentiasa sambungkan peristiwa kitar hayatnya supaya tugas yang gagal tidak menyebabkan permintaan tergantung tanpa diketahui:
'error'— pengecualian yang tidak ditangkap di dalam worker.'exit'— dicetuskan sekali;codebukan sifar apabila berlaku kegagalan.'messageerror'— mesej tidak dapat dinyahsiri.
Untuk menghentikan worker daripada bebenang utama, panggil worker.terminate() (mengembalikan janji). Worker yang tidak dihentikan akan mengekalkan proses supaya terus hidup.
const { Worker, isMainThread, parentPort } = require('node:worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('error', (err) => console.log('caught worker error:', err.message));
worker.on('exit', (code) => console.log('exited, code =', code));
} else {
throw new Error('boom inside worker');
}Bentuk Backend Praktikal
Dalam backend Node.js sebenar, anda TIDAK mencipta worker bagi setiap permintaan — permulaan bebenang memerlukan kos yang tinggi. Sebaliknya:
- Simpan kumpulan kecil worker yang berjangka hayat panjang (selalunya
os.cpus().length). - Hantar setiap tugas yang terikat pada CPU melalui
postMessagedan padankan hasil menggunakanid. - Peruntukkan worker untuk kerja yang benar-benar terikat pada CPU; kekalkan I/O pada gelung peristiwa.
Coretan ini menunjukkan protokol mesej bagi setiap worker menggunakan id untuk mengaitkan permintaan dengan respons — idea teras yang menjadi asas kepada setiap pustaka kumpulan (seperti piscina).
const { Worker, isMainThread, parentPort } = require('node:worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const pending = new Map();
let nextId = 0;
worker.on('message', ({ id, result }) => {
pending.get(id)(result);
pending.delete(id);
if (pending.size === 0) worker.terminate();
});
function submit(payload) {
return new Promise((res) => {
const id = nextId++;
pending.set(id, res);
worker.postMessage({ id, payload });
});
}
Promise.all([submit(3), submit(7)]).then((r) => console.log('results:', r));
} else {
parentPort.on('message', ({ id, payload }) => {
parentPort.postMessage({ id, result: payload * payload });
});
}Semakan Pantas
Anda perlu menghantar penimbal imej 50 MB kepada worker untuk diproses dan mahu mengelakkan penduaan memori tersebut. Pendekatan manakah yang betul?
Ringkasan
Kini anda tahu cara mencipta worker dan memindahkan data antara bebenang:
- Cipta dengan
new Worker(file, { workerData }); buat cabang berdasarkanisMainThread. - Hantar hasil dengan
parentPort.postMessage()dan terima melaluiworker.on('message')— saluran ini berfungsi dua hala. - Mesej menggunakan klon berstruktur (lebih kaya daripada JSON, tetapi tiada fungsi); penimbal besar perlu dipindahkan melalui senarai pemindahan untuk mengelakkan penyalinan.
- Cipta saluran tambahan dengan
MessageChanneldan pindahkanMessagePortuntuk saluran sampingan atau antara worker. - Sentiasa kendalikan
error/exitdan panggilterminate(); dalam pengeluaran, gunakan kumpulan dengan mesej yang dikaitkan melalui id, bukannya satu worker bagi setiap permintaan.
Pelajari JavaScript dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 22
- Pelajaran
- 92
Soalan Lazim
Adakah pelajaran “Mencipta Utas Pekerja dan Menghantar Mesej” percuma?
Ya — teks penuh “Mencipta Utas Pekerja dan Menghantar Mesej” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Kem Intensif Pembangunan Backend Node.js, tingkat taraf kepada CoddyKit PRO. Kursus Kem Intensif Pembangunan Backend Node.js merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Mencipta Utas Pekerja dan Menghantar Mesej”?
Cipta pekerja, tukar data melalui postMessage dan gunakan workerData serta MessageChannel untuk komunikasi berstruktur. Anda berlatih Kem Intensif Pembangunan Backend Node.js menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Backend Node.js?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Backend Node.js di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 2 daripada 4.
Berapa lamakah pelajaran “Mencipta Utas Pekerja dan Menghantar Mesej” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Backend Node.js ini?
Ya. Setiap pelajaran Kem Intensif Pembangunan Backend Node.js menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Mengapa Gelung Peristiwa Terhenti pada Kerja Terikat CPU
- Mencipta Utas Pekerja dan Menghantar Mesej
- Berkongsi Memori dengan SharedArrayBuffer dan Atomics
- Membina Kumpulan Pekerja Boleh Guna Semula untuk Daya Pemprosesan