Izpētiet efektīvu worker pavedienu pārvaldību JavaScript, izmantojot moduļu worker pavedienu pūlus paralēlai uzdevumu izpildei un uzlabotai lietojumprogrammu veiktspējai.
JavaScript moduļu Worker pavedienu pūls: Efektīva Worker pavedienu pārvaldība
Mūsdienu JavaScript lietojumprogrammas bieži saskaras ar veiktspējas problēmām, strādājot ar skaitļošanas ziņā intensīviem uzdevumiem vai I/O saistītām operācijām. JavaScript vienpavediena daba var ierobežot tā spēju pilnībā izmantot daudzkodolu procesorus. Par laimi, Worker Threads ieviešana Node.js un Web Workers pārlūkprogrammās nodrošina mehānismu paralēlai izpildei, ļaujot JavaScript lietojumprogrammām izmantot vairākus CPU kodolus un uzlabot reaģētspēju.
Šis bloga ieraksts iedziļinās JavaScript moduļu Worker pavedienu pūla koncepcijā, kas ir spēcīgs modelis efektīvai worker pavedienu pārvaldībai un izmantošanai. Mēs izpētīsim pavedienu pūla izmantošanas priekšrocības, apspriedīsim implementācijas detaļas un sniegsim praktiskus piemērus, lai ilustrētu tā lietojumu.
Izpratne par Worker pavedieniem
Pirms iedziļināties worker pavedienu pūla detaļās, īsi apskatīsim worker pavedienu pamatus JavaScript.
Kas ir Worker pavedieni?
Worker pavedieni ir neatkarīgi JavaScript izpildes konteksti, kas var darboties vienlaicīgi ar galveno pavedienu. Tie nodrošina veidu, kā veikt uzdevumus paralēli, nebloķējot galveno pavedienu un neizraisot UI sasalšanu vai veiktspējas pasliktināšanos.
Workeru veidi
- Web Workers: Pieejami tīmekļa pārlūkprogrammās, ļaujot fonā izpildīt skriptus, netraucējot lietotāja saskarnei. Tie ir būtiski, lai no galvenā pārlūka pavediena noņemtu smagus aprēķinus.
- Node.js Worker Threads: Ieviests Node.js, nodrošinot paralēlu JavaScript koda izpildi servera puses lietojumprogrammās. Tas ir īpaši svarīgi tādiem uzdevumiem kā attēlu apstrāde, datu analīze vai vairāku vienlaicīgu pieprasījumu apstrāde.
Galvenie jēdzieni
- Izolācija: Worker pavedieni darbojas atsevišķās atmiņas telpās no galvenā pavediena, novēršot tiešu piekļuvi koplietojamiem datiem.
- Ziņojumu nodošana: Komunikācija starp galveno pavedienu un worker pavedieniem notiek, izmantojot asinhronu ziņojumu nodošanu. Metode
postMessage()tiek izmantota datu sūtīšanai, un notikumu apstrādātājsonmessagesaņem datus. Dati ir jāserializē/jādeserializē, kad tos nodod starp pavedieniem. - Moduļu Workeri: Workeri, kas izveidoti, izmantojot ES moduļus (
import/exportsintakse). Tie piedāvā labāku koda organizāciju un atkarību pārvaldību salīdzinājumā ar klasiskajiem skriptu workeriem.
Worker pavedienu pūla izmantošanas priekšrocības
Lai gan worker pavedieni piedāvā spēcīgu mehānismu paralēlai izpildei, to tieša pārvaldīšana var būt sarežģīta un neefektīva. Worker pavedienu izveidošana un iznīcināšana katram uzdevumam var radīt ievērojamas papildu izmaksas. Šeit noder worker pavedienu pūls.
Worker pavedienu pūls ir iepriekš izveidotu worker pavedienu kolekcija, kas tiek uzturēti dzīvi un gatavi izpildīt uzdevumus. Kad uzdevums ir jāapstrādā, tas tiek iesniegts pūlā, kas to piešķir pieejamam worker pavedienam. Kad uzdevums ir pabeigts, worker pavediens atgriežas pūlā, gatavs apstrādāt citu uzdevumu.
Worker pavedienu pūla izmantošanas priekšrocības:
- Samazinātas papildu izmaksas: Atkārtoti izmantojot esošos worker pavedienus, tiek novērstas papildu izmaksas, kas saistītas ar pavedienu izveidi un iznīcināšanu katram uzdevumam, kas noved pie ievērojamiem veiktspējas uzlabojumiem, īpaši īslaicīgiem uzdevumiem.
- Uzlabota resursu pārvaldība: Pūls ierobežo vienlaicīgo worker pavedienu skaitu, novēršot pārmērīgu resursu patēriņu un iespējamu sistēmas pārslodzi. Tas ir būtiski, lai nodrošinātu stabilitāti un novērstu veiktspējas pasliktināšanos lielas slodzes apstākļos.
- Vienkāršota uzdevumu pārvaldība: Pūls nodrošina centralizētu mehānismu uzdevumu pārvaldībai un plānošanai, vienkāršojot lietojumprogrammas loģiku un uzlabojot koda uzturējamību. Tā vietā, lai pārvaldītu atsevišķus worker pavedienus, jūs mijiedarbojaties ar pūlu.
- Kontrolēts vienlaicīgums: Jūs varat konfigurēt pūlu ar noteiktu pavedienu skaitu, ierobežojot paralēlisma pakāpi un novēršot resursu izsmelšanu. Tas ļauj precīzi noregulēt veiktspēju, pamatojoties uz pieejamajiem aparatūras resursiem un darba slodzes īpašībām.
- Uzlabota reaģētspēja: Pārvirzot uzdevumus uz worker pavedieniem, galvenais pavediens paliek reaģētspējīgs, nodrošinot vienmērīgu lietotāja pieredzi. Tas ir īpaši svarīgi interaktīvām lietojumprogrammām, kur UI reaģētspēja ir kritiska.
JavaScript moduļu Worker pavedienu pūla implementācija
Izpētīsim JavaScript moduļu Worker pavedienu pūla implementāciju. Mēs aplūkosim galvenās sastāvdaļas un sniegsim koda piemērus, lai ilustrētu implementācijas detaļas.
Galvenās sastāvdaļas
- Worker pūla klase (Worker Pool Class): Šī klase ietver loģiku worker pavedienu pūla pārvaldībai. Tā ir atbildīga par worker pavedienu izveidi, inicializāciju un pārstrādi.
- Uzdevumu rinda (Task Queue): Rinda, kurā tiek glabāti uzdevumi, kas gaida izpildi. Uzdevumi tiek pievienoti rindai, kad tie tiek iesniegti pūlā.
- Worker pavediena ietvars (Worker Thread Wrapper): Ietvars ap natīvo worker pavediena objektu, nodrošinot ērtu saskarni mijiedarbībai ar workeru. Šis ietvars var apstrādāt ziņojumu nodošanu, kļūdu apstrādi un uzdevumu pabeigšanas izsekošanu.
- Uzdevumu iesniegšanas mehānisms (Task Submission Mechanism): Mehānisms uzdevumu iesniegšanai pūlā, parasti metode Worker pūla klasē. Šī metode pievieno uzdevumu rindai un signalizē pūlam, lai to piešķirtu pieejamam worker pavedienam.
Koda piemērs (Node.js)
Šeit ir vienkārša worker pavedienu pūla implementācijas piemērs Node.js, izmantojot moduļu workerus:
// worker_pool.js
import { Worker } from 'worker_threads';
class WorkerPool {
constructor(numWorkers, workerFile) {
this.numWorkers = numWorkers;
this.workerFile = workerFile;
this.workers = [];
this.taskQueue = [];
this.availableWorkers = [];
for (let i = 0; i < numWorkers; i++) {
const worker = new Worker(workerFile, { type: 'module' });
const workerWrapper = {
worker,
isBusy: false
};
this.workers.push(workerWrapper);
this.availableWorkers.push(workerWrapper);
worker.on('message', (message) => {
// Handle task completion
workerWrapper.isBusy = false;
this.availableWorkers.push(workerWrapper);
this.processTaskQueue();
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
worker.on('exit', (code) => {
if (code !== 0) {
console.error(`Worker stopped with exit code ${code}`);
}
});
}
}
runTask(task) {
return new Promise((resolve, reject) => {
this.taskQueue.push({ task, resolve, reject });
this.processTaskQueue();
});
}
processTaskQueue() {
if (this.taskQueue.length === 0 || this.availableWorkers.length === 0) {
return;
}
const workerWrapper = this.availableWorkers.shift();
const { task, resolve, reject } = this.taskQueue.shift();
workerWrapper.isBusy = true;
workerWrapper.worker.postMessage(task);
workerWrapper.worker.once('message', (result) => {
resolve(result);
});
workerWrapper.worker.once('error', (error) => {
reject(error);
});
}
close() {
this.workers.forEach(workerWrapper => workerWrapper.worker.terminate());
}
}
export default WorkerPool;
// worker.js
import { parentPort } from 'worker_threads';
parentPort.on('message', (task) => {
// Simulate a computationally intensive task
const result = task * 2; // Replace with your actual task logic
parentPort.postMessage(result);
});
// main.js
import WorkerPool from './worker_pool.js';
const numWorkers = 4; // Adjust based on your CPU core count
const workerFile = './worker.js';
const pool = new WorkerPool(numWorkers, workerFile);
async function main() {
const tasks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const results = await Promise.all(
tasks.map(async (task) => {
try {
const result = await pool.runTask(task);
console.log(`Task ${task} result: ${result}`);
return result;
} catch (error) {
console.error(`Task ${task} failed:`, error);
return null;
}
})
);
console.log('All tasks completed:', results);
pool.close(); // Terminate all workers in the pool
}
main();
Paskaidrojums:
- worker_pool.js: Definē
WorkerPoolklasi, kas pārvalda worker pavedienu izveidi, uzdevumu rindošanu un uzdevumu piešķiršanu. MetoderunTaskiesniedz uzdevumu rindā, unprocessTaskQueuepiešķir uzdevumus pieejamiem workeriem. Tā arī apstrādā workeru kļūdas un iziešanu. - worker.js: Šis ir worker pavediena kods. Tas klausās ziņojumus no galvenā pavediena, izmantojot
parentPort.on('message'), veic uzdevumu un nosūta rezultātu atpakaļ, izmantojotparentPort.postMessage(). Sniegtais piemērs vienkārši reizina saņemto uzdevumu ar 2. - main.js: Demonstrē, kā izmantot
WorkerPool. Tas izveido pūlu ar noteiktu workeru skaitu un iesniedz uzdevumus pūlā, izmantojotpool.runTask(). Tas gaida, līdz visi uzdevumi ir pabeigti, izmantojotPromise.all(), un pēc tam aizver pūlu.
Koda piemērs (Web Workers)
Tas pats koncepts attiecas uz Web Workeriem pārlūkprogrammā. Tomēr implementācijas detaļas nedaudz atšķiras pārlūkprogrammas vides dēļ. Šeit ir konceptuāls izklāsts. Ņemiet vērā, ka, darbinot lokāli, var rasties CORS problēmas, ja faili netiek pasniegti caur serveri (piemēram, izmantojot `npx serve`).
// worker_pool.js (for browser)
class WorkerPool {
constructor(numWorkers, workerFile) {
this.numWorkers = numWorkers;
this.workerFile = workerFile;
this.workers = [];
this.taskQueue = [];
this.availableWorkers = [];
for (let i = 0; i < numWorkers; i++) {
const worker = new Worker(workerFile, { type: 'module' });
const workerWrapper = {
worker,
isBusy: false
};
this.workers.push(workerWrapper);
this.availableWorkers.push(workerWrapper);
worker.onmessage = (event) => {
// Handle task completion
workerWrapper.isBusy = false;
this.availableWorkers.push(workerWrapper);
this.processTaskQueue();
};
worker.onerror = (error) => {
console.error('Worker error:', error);
};
}
}
runTask(task) {
return new Promise((resolve, reject) => {
this.taskQueue.push({ task, resolve, reject });
this.processTaskQueue();
});
}
processTaskQueue() {
if (this.taskQueue.length === 0 || this.availableWorkers.length === 0) {
return;
}
const workerWrapper = this.availableWorkers.shift();
const { task, resolve, reject } = this.taskQueue.shift();
workerWrapper.isBusy = true;
workerWrapper.worker.postMessage(task);
workerWrapper.worker.onmessage = (event) => {
resolve(event.data);
};
workerWrapper.worker.onerror = (error) => {
reject(error);
};
}
close() {
this.workers.forEach(workerWrapper => workerWrapper.worker.terminate());
}
}
export default WorkerPool;
// worker.js (for browser)
self.onmessage = (event) => {
const task = event.data;
// Simulate a computationally intensive task
const result = task * 2; // Replace with your actual task logic
self.postMessage(result);
};
// main.js (for browser, included in your HTML)
import WorkerPool from './worker_pool.js';
const numWorkers = 4; // Adjust based on your CPU core count
const workerFile = './worker.js';
const pool = new WorkerPool(numWorkers, workerFile);
async function main() {
const tasks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const results = await Promise.all(
tasks.map(async (task) => {
try {
const result = await pool.runTask(task);
console.log(`Task ${task} result: ${result}`);
return result;
} catch (error) {
console.error(`Task ${task} failed:`, error);
return null;
}
})
);
console.log('All tasks completed:', results);
pool.close(); // Terminate all workers in the pool
}
main();
Galvenās atšķirības pārlūkprogrammā:
- Web Workeri tiek izveidoti tieši ar
new Worker(workerFile). - Ziņojumu apstrādei tiek izmantoti
worker.onmessageunself.onmessage(workera iekšienē). parentPortAPI no Node.jsworker_threadsmoduļa nav pieejams pārlūkprogrammās.- Pārliecinieties, ka jūsu faili tiek pasniegti ar pareiziem MIME tipiem, īpaši JavaScript moduļiem (
type="module").
Praktiski piemēri un lietošanas gadījumi
Izpētīsim dažus praktiskus piemērus un lietošanas gadījumus, kur worker pavedienu pūls var ievērojami uzlabot veiktspēju.
Attēlu apstrāde
Attēlu apstrādes uzdevumi, piemēram, izmēru maiņa, filtrēšana vai formāta konvertēšana, var būt skaitļošanas ziņā intensīvi. Šo uzdevumu pārvirzīšana uz worker pavedieniem ļauj galvenajam pavedienam palikt reaģētspējīgam, nodrošinot vienmērīgāku lietotāja pieredzi, īpaši tīmekļa lietojumprogrammām.
Piemērs: Tīmekļa lietojumprogramma, kas ļauj lietotājiem augšupielādēt un rediģēt attēlus. Izmēru maiņu un filtru lietošanu var veikt worker pavedienos, novēršot UI sasalšanu, kamēr attēls tiek apstrādāts.
Datu analīze
Lielu datu kopu analīze var būt laikietilpīga un resursietilpīga. Worker pavedienus var izmantot, lai paralelizētu datu analīzes uzdevumus, piemēram, datu apkopošanu, statistiskos aprēķinus vai mašīnmācīšanās modeļu apmācību.
Piemērs: Datu analīzes lietojumprogramma, kas apstrādā finanšu datus. Tādus aprēķinus kā slīdošie vidējie, tendenču analīze un riska novērtēšana var veikt paralēli, izmantojot worker pavedienus.
Reāllaika datu straumēšana
Lietojumprogrammas, kas apstrādā reāllaika datu straumes, piemēram, finanšu tirgus datus vai sensoru datus, var gūt labumu no worker pavedieniem. Worker pavedienus var izmantot, lai apstrādātu un analizētu ienākošās datu straumes, nebloķējot galveno pavedienu.
Piemērs: Reāllaika akciju tirgus rādītājs, kas parāda cenu atjauninājumus un diagrammas. Datu apstrādi, diagrammu renderēšanu un brīdinājumu paziņojumus var apstrādāt worker pavedienos, nodrošinot, ka UI paliek reaģētspējīgs pat pie liela datu apjoma.
Fona uzdevumu apstrāde
Jebkuru fona uzdevumu, kas neprasa tūlītēju lietotāja mijiedarbību, var pārvirzīt uz worker pavedieniem. Piemēri ietver e-pasta sūtīšanu, atskaišu ģenerēšanu vai plānotu dublējumu veikšanu.
Piemērs: Tīmekļa lietojumprogramma, kas izsūta iknedēļas e-pasta jaunumus. E-pasta sūtīšanas procesu var apstrādāt worker pavedienos, novēršot galvenā pavediena bloķēšanu un nodrošinot, ka vietne paliek reaģētspējīga.
Vairāku vienlaicīgu pieprasījumu apstrāde (Node.js)
Node.js servera lietojumprogrammās worker pavedienus var izmantot, lai paralēli apstrādātu vairākus vienlaicīgus pieprasījumus. Tas var uzlabot kopējo caurlaidspēju un samazināt atbildes laiku, īpaši lietojumprogrammām, kas veic skaitļošanas ziņā intensīvus uzdevumus.
Piemērs: Node.js API serveris, kas apstrādā lietotāju pieprasījumus. Attēlu apstrādi, datu validāciju un datu bāzes vaicājumus var apstrādāt worker pavedienos, ļaujot serverim apstrādāt vairāk vienlaicīgu pieprasījumu bez veiktspējas pasliktināšanās.
Worker pavedienu pūla veiktspējas optimizēšana
Lai maksimāli izmantotu worker pavedienu pūla priekšrocības, ir svarīgi optimizēt tā veiktspēju. Šeit ir daži padomi un metodes:
- Izvēlieties pareizo workeru skaitu: Optimālais worker pavedienu skaits ir atkarīgs no pieejamo CPU kodolu skaita un darba slodzes īpašībām. Vispārīgs ieteikums ir sākt ar workeru skaitu, kas vienāds ar CPU kodolu skaitu, un pēc tam pielāgot, pamatojoties uz veiktspējas testēšanu. Rīki, piemēram, `os.cpus()` Node.js, var palīdzēt noteikt kodolu skaitu. Pārmērīgs pavedienu skaits var radīt konteksta pārslēgšanas papildu izmaksas, noliedzot paralēlisma priekšrocības.
- Minimizējiet datu pārsūtīšanu: Datu pārsūtīšana starp galveno pavedienu un worker pavedieniem var būt veiktspējas problēma. Minimizējiet pārsūtāmo datu apjomu, apstrādājot pēc iespējas vairāk datu worker pavedienā. Apsveriet iespēju izmantot SharedArrayBuffer (ar atbilstošiem sinhronizācijas mehānismiem), lai tieši koplietotu datus starp pavedieniem, ja iespējams, taču apzinieties drošības sekas un pārlūkprogrammu saderību.
- Optimizējiet uzdevumu granularitāti: Atsevišķu uzdevumu lielums un sarežģītība var ietekmēt veiktspēju. Sadaliet lielus uzdevumus mazākās, vieglāk pārvaldāmās vienībās, lai uzlabotu paralēlismu un samazinātu ilgstošu uzdevumu ietekmi. Tomēr izvairieties no pārāk daudzu mazu uzdevumu izveides, jo uzdevumu plānošanas un komunikācijas papildu izmaksas var pārsniegt paralēlisma priekšrocības.
- Izvairieties no bloķējošām operācijām: Izvairieties no bloķējošu operāciju veikšanas worker pavedienos, jo tas var liegt workeram apstrādāt citus uzdevumus. Izmantojiet asinhronas I/O operācijas un nebloķējošus algoritmus, lai uzturētu worker pavediena reaģētspēju.
- Pārraugiet un profilējiet veiktspēju: Izmantojiet veiktspējas uzraudzības rīkus, lai identificētu problēmas un optimizētu worker pavedienu pūlu. Rīki, piemēram, Node.js iebūvētais profileris vai pārlūkprogrammas izstrādātāju rīki, var sniegt ieskatu par CPU lietojumu, atmiņas patēriņu un uzdevumu izpildes laikiem.
- Kļūdu apstrāde: Implementējiet robustus kļūdu apstrādes mehānismus, lai notvertu un apstrādātu kļūdas, kas rodas worker pavedienos. Nenotvertas kļūdas var izraisīt worker pavediena un, iespējams, visas lietojumprogrammas avāriju.
Alternatīvas Worker pavedienu pūliem
Lai gan worker pavedienu pūli ir spēcīgs rīks, pastāv alternatīvas pieejas vienlaicīguma un paralēlisma sasniegšanai JavaScript.
- Asinhronā programmēšana ar Promises un Async/Await: Asinhronā programmēšana ļauj veikt nebloķējošas operācijas, neizmantojot worker pavedienus. Promises un async/await nodrošina strukturētāku un lasāmāku veidu, kā apstrādāt asinhronu kodu. Tas ir piemērots I/O saistītām operācijām, kur jūs gaidāt ārējus resursus (piem., tīkla pieprasījumi, datu bāzes vaicājumi).
- WebAssembly (Wasm): WebAssembly ir binārs instrukciju formāts, kas ļauj palaist kodu, kas rakstīts citās valodās (piem., C++, Rust), tīmekļa pārlūkprogrammās. Wasm var nodrošināt ievērojamus veiktspējas uzlabojumus skaitļošanas ziņā intensīviem uzdevumiem, īpaši kombinācijā ar worker pavedieniem. Jūs varat pārvirzīt CPU-intensīvās lietojumprogrammas daļas uz Wasm moduļiem, kas darbojas worker pavedienos.
- Service Workers: Galvenokārt izmantoti kešatmiņai un fona sinhronizācijai tīmekļa lietojumprogrammās, Service Workers var tikt izmantoti arī vispārējai fona apstrādei. Tomēr tie galvenokārt ir paredzēti tīkla pieprasījumu un kešatmiņas apstrādei, nevis skaitļošanas ziņā intensīviem uzdevumiem.
- Ziņojumu rindas (piem., RabbitMQ, Kafka): Sadalītās sistēmās ziņojumu rindas var izmantot, lai pārvirzītu uzdevumus uz atsevišķiem procesiem vai serveriem. Tas ļauj mērogot jūsu lietojumprogrammu horizontāli un apstrādāt lielu uzdevumu apjomu. Šis ir sarežģītāks risinājums, kas prasa infrastruktūras iestatīšanu un pārvaldību.
- Bezservera funkcijas (piem., AWS Lambda, Google Cloud Functions): Bezservera funkcijas ļauj palaist kodu mākonī, nepārvaldot serverus. Jūs varat izmantot bezservera funkcijas, lai pārvirzītu skaitļošanas ziņā intensīvus uzdevumus uz mākoni un mērogotu savu lietojumprogrammu pēc pieprasījuma. Šī ir laba iespēja uzdevumiem, kas ir reti vai prasa ievērojamus resursus.
Noslēgums
JavaScript moduļu Worker pavedienu pūli nodrošina spēcīgu un efektīvu mehānismu worker pavedienu pārvaldībai un paralēlās izpildes izmantošanai. Samazinot papildu izmaksas, uzlabojot resursu pārvaldību un vienkāršojot uzdevumu pārvaldību, worker pavedienu pūli var ievērojami uzlabot JavaScript lietojumprogrammu veiktspēju un reaģētspēju.
Lemjot, vai izmantot worker pavedienu pūlu, apsveriet šādus faktorus:
- Uzdevumu sarežģītība: Worker pavedieni ir visnoderīgākie CPU saistītiem uzdevumiem, kurus var viegli paralelizēt.
- Uzdevumu biežums: Ja uzdevumi tiek izpildīti bieži, worker pavedienu izveides un iznīcināšanas papildu izmaksas var būt ievērojamas. Pavedienu pūls palīdz to mazināt.
- Resursu ierobežojumi: Apsveriet pieejamos CPU kodolus un atmiņu. Neveidojiet vairāk worker pavedienu, nekā jūsu sistēma spēj apstrādāt.
- Alternatīvi risinājumi: Novērtējiet, vai asinhronā programmēšana, WebAssembly vai citas vienlaicīguma metodes varētu būt labāk piemērotas jūsu konkrētajam lietošanas gadījumam.
Izprotot worker pavedienu pūlu priekšrocības un implementācijas detaļas, izstrādātāji var tos efektīvi izmantot, lai veidotu augstas veiktspējas, reaģētspējīgas un mērogojamas JavaScript lietojumprogrammas.
Atcerieties rūpīgi testēt un salīdzināt savas lietojumprogrammas veiktspēju ar un bez worker pavedieniem, lai nodrošinātu, ka jūs sasniedzat vēlamos veiktspējas uzlabojumus. Optimālā konfigurācija var atšķirties atkarībā no konkrētās darba slodzes un aparatūras resursiem.
Turpmāka izpēte par progresīvām metodēm, piemēram, SharedArrayBuffer un Atomics (sinhronizācijai), var atklāt vēl lielāku potenciālu veiktspējas optimizācijai, izmantojot worker pavedienus.