Worker-säikeiden käynnistäminen ja viestien välittäminen
Luo workereita, vaihda dataa postMessagella ja käytä workerDataa ja MessageChannelia rakenteiseen viestintään.
Worker-säikeiden käynnistäminen ja viestien välittäminen on ilmainen Node.js-taustakehityksen bootcamp-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Node.js-taustakehityksen bootcamp-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Miksi Worker Threads on olemassa
Node.js suorittaa JavaScriptiä yhdessä säikeessä. Tämä sopii erinomaisesti I/O:ta kuormittavaan työhön (HTTP, tietokannat ja tiedostot), koska tapahtumasilmukka ei koskaan esty. Mutta suoritinta kuormittava tehtävä — kuvien koon muuttaminen, tiivisteiden laskeminen, valtavien aineistojen jäsentäminen tai kryptografia — suoritetaan synkronisesti ja jäädyttää koko palvelimen, kunnes se valmistuu.
- I/O:ta kuormittava → pysy tapahtumasilmukassa ja käytä asynkronisia rajapintoja.
- Suoritinta kuormittava → siirrä työ
Worker-säikeeseen, jotta pääsäie voi jatkaa pyyntöjen käsittelyä.
worker_threads-moduulin avulla JavaScriptiä voi suorittaa rinnakkain erillisissä käyttöjärjestelmäsäikeissä, joilla jokaisella on oma V8-eristysympäristönsä ja tapahtumasilmukkansa.
Ensimmäisen työntekijän luominen
Säie luodaan komennolla new Worker(filename). Tiedosto suoritetaan uudessa säikeessä. isMainThread-arvon avulla yksi tiedosto voi toimia eri tavalla sen mukaan, missä se suoritetaan.
Alla pääsäie luo työntekijän, joka viittaa takaisin samaan tiedostoon. Työntekijähaara suorittaa työn ja lopettaa.
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);
}Tietojen lähettäminen workerData-arvolla
Kun syöte halutaan antaa työntekijälle sen luonnin yhteydessä, välittäkää workerData options-oliossa. Työntekijän sisällä se luetaan worker_threads-moduulista.
workerDatakloonataan (structured clone), eikä sitä jaeta — sen muuttaminen työntekijässä ei vaikuta pääsäikeeseen.- Käyttäkää sitä alkuperäisiin työparametreihin, kuten tiedostopolkuun, lukualueeseen tai asetuksiin.
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);
}Tulosten vastaanottaminen postMessage-kutsulla
workerData on yksisuuntainen ja kertaluonteinen. Käyttäkää tuloksia varten viestikanavaa, joka on sisäänrakennettu jokaiseen työntekijään:
- Työntekijän sisällä:
parentPort.postMessage(value)lähettää tiedot pääprosessille. - Pääsäikeessä:
worker.on('message', cb)vastaanottaa ne.
Viestit ovat asynkronisia ja structured clone -menetelmällä kloonattuja, joten pääsäie pysyy vastaanottavana työntekijän laskiessa tulosta.
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));
}Kaksisuuntainen viestintä
Kanava toimii molempiin suuntiin. Pääprosessi voi kutsua worker.postMessage()-metodia, ja työntekijä kuuntelee viestejä komennolla parentPort.on('message', ...). Näin työntekijästä tulee pitkäikäinen palvelu, joka käsittelee useita tehtäviä yhden sijaan.
Huomaatte, että työntekijä pysyy käynnissä kuunnellen viestejä. Teidän on ilmoitettava sille erikseen, milloin se lopettaa (tässä null-sentinelli käynnistää process.exit-kutsun).
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);
});
}Yksittäisen tehtävän muuntaminen Promiseksi
Taustapalvelun koodissa tarvitaan yleensä selkeä asynkroninen funktio: kutsukaa sitä ja käyttäkää tulokseen await-lausetta. Kääritkää työntekijän elinkaari Promise-olioon, joka ratkaistaan message-tapahtuman yhteydessä ja hylätään error-tapahtuman tai nollasta poikkeavan exit-koodin yhteydessä.
Tämä malli muodostaa jokaisen työntekijäpoolin perustan: yksi promise tehtävää kohden.
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);
}Structured clone: mitä voi lähettää
Viestit kopioidaan structured clone -algoritmilla, ei JSON.stringify-menetelmällä. Siksi lähetettävissä on muutakin kuin tavallista JSON-dataa.
- Tuettuja ovat: oliot, taulukot,
Map,Set,Date,RegExp,ArrayBuffer, tyypitetyt taulukot jaBigInt. - Ei tuettuja ovat: funktiot, metodeja sisältävät luokkainstanssit, DOM-tyyppiset oliot ja kaikki sulkeumia sisältävä — nämä aiheuttavat
DataCloneError-virheen.
Suurten olioiden kloonaaminen kuluttaa suoritinaikaa ja muistia. Suurten binääripuskureiden kanssa kannattaa seuraavassa osiossa käsiteltävällä tavalla suosia siirtämistä kopioinnin sijaan.
Puskureiden siirtäminen kopioinnin sijaan
Suurten binääritietojen kopiointi on tehotonta. Välittäkää postMessage-kutsun toisena argumenttina siirtoluettelo. ArrayBuffer-olion omistajuus siirtyy toiseen säikeeseen — ilman kopiointia — ja puskurista tulee käyttökelvoton lähettäjässä (sen byteLength-arvoksi tulee 0).
Käyttäkää tätä kuvadataan, tiedostolohkoihin tai mihin tahansa megatavujen kokoiseen aineistoon, jotta muistia ei tarvitse monistaa.
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 sivukanavia varten
Sisäänrakennetun parentPort-portin lisäksi voitte luoda oman kanavan komennolla new MessageChannel(). Se tarjoaa kaksi toisiinsa yhdistettyä porttia, port1 ja port2. Pitäkää toinen itsellänne, siirtäkää toinen työntekijälle, ja käytössänne on erillinen putki — hyödyllinen esimerkiksi ohjausviestien ja datan erottamiseen tai työntekijöiden väliseen viestintään.
MessagePort on itsekin siirrettävä, joten sen voi lähettää workerData-arvon sisällä tai viestissä siirtoluettelon avulla.
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 + ')');
});
}Virheiden käsittely ja hallittu lopetus
Työntekijä voi kaatua. Kytkekää aina sen elinkaaritapahtumat, jotta epäonnistunut tehtävä ei jää huomaamatta odottamaan pyyntöä:
'error'— työntekijässä tapahtunut käsittelemätön poikkeus.'exit'— suoritetaan kerran;codeon virhetilanteessa nollasta poikkeava.'messageerror'— viestiä ei voitu jäsentää.
Voitte pysäyttää työntekijän pääsäikeestä kutsumalla worker.terminate()-metodia (se palauttaa promisen). Lopettamatta jätetyt työntekijät pitävät prosessin käynnissä.
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');
}Käytännön taustapalvelun rakenne
Oikeassa Node.js-taustapalvelussa ette luo uutta työntekijää jokaista pyyntöä varten — säikeen käynnistäminen on kallista. Sen sijaan:
- Pidätte pienen joukon pooliin kuuluvia pitkäikäisiä työntekijöitä (usein
os.cpus().length). - Lähetätte jokaisen suorittimen kuormittavan tehtävän
postMessage-kutsulla ja yhdistätte tulokset pyyntöihinid-tunnisteen avulla. - Varaatte työntekijät aidosti suorittimen kuormittavaan työhön ja jätätte I/O:n tapahtumasilmukan käsiteltäväksi.
Tämä koodikatkelma näyttää työntekijäkohtaisen viestiprotokollan, jossa id-tunnistetta käytetään pyynnön ja vastauksen yhdistämiseen — kyseessä on jokaisen poolikirjaston (kuten piscina) perustana oleva ydinajatus.
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 });
});
}Pikatarkistus
Teidän on lähetettävä 50 MB:n kuvapuskuri työntekijälle käsiteltäväksi, ja haluatte välttää muistin monistamisen. Mikä toimintatapa on oikea?
Kertaus
Nyt osaatte luoda työntekijöitä ja siirtää tietoja säikeiden välillä:
- Luokaa työntekijä komennolla
new Worker(file, { workerData })ja haarautukaaisMainThread-arvon perusteella. - Lähettäkää tulokset komennolla
parentPort.postMessage()ja vastaanottakaa neworker.on('message')-kuuntelijalla — kanava on kaksisuuntainen. - Viestit käyttävät structured clone -menetelmää (monipuolisempi kuin JSON, mutta funktioita ei tueta); suuret puskurit kannattaa siirtää siirtoluettelon avulla ilman kopiointia.
- Luokaa ylimääräisiä putkia
MessageChannel-kanavalla ja siirtäkääMessagePortsivu- tai työntekijöiden välisiä kanavia varten. - Käsitelkää aina
error/exit-tapahtumat ja kutsukaaterminate()-metodia; tuotannossa käyttäkää poolia, jossa viestit yhdistetäänid-tunnisteilla, yhden työntekijän sijaan pyyntöä kohden.
Opi JavaScript tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 22
- Oppitunnit
- 92
Usein kysytyt kysymykset
Onko oppitunti ”Worker-säikeiden käynnistäminen ja viestien välittäminen” ilmainen?
Kyllä – oppitunnin ”Worker-säikeiden käynnistäminen ja viestien välittäminen” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Node.js-taustakehityksen bootcamp-kurssin, päivitä CoddyKit PROhon. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Worker-säikeiden käynnistäminen ja viestien välittäminen”?
Luo workereita, vaihda dataa postMessagella ja käytä workerDataa ja MessageChannelia rakenteiseen viestintään. Harjoittelet Node.js-taustakehityksen bootcamp-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Node.js-taustakehityksen bootcamp-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Node.js-taustakehityksen bootcamp-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.
Kuinka kauan ”Worker-säikeiden käynnistäminen ja viestien välittäminen”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Node.js-taustakehityksen bootcamp-oppitunnilla?
Kyllä. Jokainen Node.js-taustakehityksen bootcamp-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Miksi tapahtumasilmukka pysähtyy suorittimen kuormittuessa
- Worker-säikeiden käynnistäminen ja viestien välittäminen
- Muistin jakaminen SharedArrayBufferilla ja Atomicsilla
- Uudelleenkäytettävän worker-poolin rakentaminen suorituskykyä varten