Baru-baru ini, versi 10.5.0 platform Node.js telah dirilis. Salah satu fitur utamanya adalah dukungan untuk bekerja dengan stream yang pertama kali ditambahkan ke Node.js, sementara masih bersifat eksperimental. Fakta ini sangat menarik mengingat fakta bahwa platform sekarang memiliki kesempatan ini, yang penganutnya selalu bangga dengan fakta bahwa platform tersebut tidak memerlukan stream karena subsistem I / O asinkron yang fantastis. Namun, dukungan utas telah muncul di Node.js. Kenapa bisa begitu? Kepada siapa dan mengapa mereka bisa berguna?

Singkatnya, ini diperlukan agar platform Node.js dapat mencapai ketinggian baru di area-area di mana sebelumnya Node.js tidak menunjukkan hasil yang paling luar biasa. Kita berbicara tentang melakukan perhitungan yang menggunakan sumber daya prosesor secara intensif. Ini terutama alasan bahwa Node.js tidak terlalu kuat di bidang-bidang seperti kecerdasan buatan, pembelajaran mesin, dan pemrosesan data dalam jumlah besar. Banyak upaya telah diarahkan untuk memungkinkan Node.js menunjukkan dirinya dengan baik dalam memecahkan masalah seperti itu, tetapi di sini platform ini masih terlihat jauh lebih sederhana daripada, misalnya, dalam pengembangan layanan-layanan mikronya.
Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa ia memutuskan untuk mengurangi dokumentasi teknis, yang dapat ditemukan dalam
permintaan tarik asli dan dalam
sumber resmi , ke serangkaian contoh praktis sederhana. Dia berharap bahwa siapa pun yang melihat contoh-contoh ini akan cukup tahu untuk memulai dengan utas di Node.js.
Tentang modul worker_threads dan flag --experimental-worker
Dukungan multithreading di Node.js diimplementasikan sebagai module
worker_threads
. Oleh karena itu, untuk memanfaatkan fitur baru, modul ini harus terhubung menggunakan perintah yang
require
.
Perhatikan bahwa Anda hanya dapat bekerja dengan
worker_threads
menggunakan
worker_threads
-
experimental-worker
ketika menjalankan skrip, jika tidak, sistem tidak akan menemukan modul ini.
Perhatikan bahwa bendera menyertakan kata "pekerja", bukan "utas". Apa yang sebenarnya kita bicarakan disebutkan dalam dokumentasi, yang menggunakan istilah “utas pekerja” (utas pekerja) atau hanya “pekerja” (pekerja). Di masa depan, kami akan mengikuti pendekatan yang sama.
Jika Anda telah menulis kode multi-utas, maka, menjelajahi fitur-fitur baru Node.js, Anda akan melihat banyak hal yang sudah Anda kenal. Jika Anda belum pernah bekerja dengan hal seperti ini sebelumnya, teruskan membaca lebih lanjut, karena penjelasan yang tepat untuk pendatang baru akan diberikan di sini.
Tentang tugas yang bisa diselesaikan dengan bantuan pekerja di Node.js
Alur pekerja dimaksudkan, seperti yang telah disebutkan, untuk menyelesaikan tugas yang secara intensif menggunakan kemampuan prosesor. Perlu dicatat bahwa penggunaan mereka untuk memecahkan masalah I / O adalah pemborosan sumber daya, karena, menurut dokumentasi resmi, mekanisme Node.js internal yang bertujuan mengatur I / O asinkron jauh lebih efisien dalam diri mereka daripada menggunakan memecahkan masalah aliran pekerja yang sama. Oleh karena itu, kami segera memutuskan bahwa kami tidak akan berurusan dengan input dan output data menggunakan pekerja.
Mari kita mulai dengan contoh sederhana yang menunjukkan cara membuat dan menggunakan pekerja.
Contoh No. 1
const { Worker, isMainThread, workerData } = require('worker_threads'); let currentVal = 0; let intervals = [100,1000, 500] function counter(id, i){ console.log("[", id, "]", i) return i; } if(isMainThread) { console.log("this is the main thread") for(let i = 0; i < 2; i++) { let w = new Worker(__filename, {workerData: i}); } setInterval((a) => currentVal = counter(a,currentVal + 1), intervals[2], "MainThread"); } else { console.log("this isn't") setInterval((a) => currentVal = counter(a,currentVal + 1), intervals[workerData], workerData); }
Output dari kode ini akan terlihat seperti serangkaian garis yang menunjukkan penghitung yang nilainya meningkat pada kecepatan yang berbeda.
Hasil contoh pertamaKami akan berurusan dengan apa yang terjadi di sini:
- Instruksi di dalam ekspresi
if
membuat 2 utas, kode yang, berkat parameter __filename
, diambil dari skrip yang sama yang dilewati Node.js saat contoh dijalankan. Sekarang pekerja memerlukan path lengkap ke file dengan kode, mereka tidak mendukung path relatif, itulah sebabnya nilai ini digunakan di sini.
- Data untuk dua pekerja ini dikirim sebagai parameter global, dalam bentuk atribut data
workerData
, yang digunakan dalam argumen kedua. Setelah itu, akses ke nilai ini dapat diperoleh melalui konstanta dengan nama yang sama (perhatikan bagaimana konstanta yang sesuai dibuat di baris pertama file, dan bagaimana, di baris terakhir, digunakan).
Berikut ini adalah contoh yang sangat sederhana menggunakan modul
worker_threads
, belum ada yang menarik yang terjadi di sini. Karena itu, pertimbangkan contoh lain.
Contoh No. 2
Perhatikan contoh di mana, pertama, kami akan melakukan beberapa perhitungan "berat", dan kedua, melakukan sesuatu yang tidak sinkron di utas utama.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); const request = require("request"); if(isMainThread) { console.log("This is the main thread") let w = new Worker(__filename, {workerData: null}); w.on('message', (msg) => { // ! console.log("First value is: ", msg.val); console.log("Took: ", (msg.timeDiff / 1000), " seconds"); }) w.on('error', console.error); w.on('exit', (code) => { if(code != 0) console.error(new Error(`Worker stopped with exit code ${code}`)) }); request.get('http://www.google.com', (err, resp) => { if(err) { return console.error(err); } console.log("Total bytes received: ", resp.body.length); }) } else { // function random(min, max) { return Math.random() * (max - min) + min } const sorter = require("./list-sorter"); const start = Date.now() let bigList = Array(1000000).fill().map( (_) => random(1,10000)) sorter.sort(bigList); parentPort.postMessage({ val: sorter.firstValue, timeDiff: Date.now() - start}); }
Untuk menjalankan contoh ini, perhatikan fakta bahwa kode ini membutuhkan modul
request
(dapat diinstal menggunakan npm, misalnya, menggunakan perintah
npm init --yes
dan
npm install request --save
di direktori kosong dengan file yang berisi kode di atas
npm install request --save
), dan fakta bahwa ia menggunakan modul tambahan, yang dihubungkan oleh command
const sorter = require("./list-sorter");
. File modul ini (
list-sorter.js
) harus berada di tempat yang sama dengan file yang dijelaskan di atas, kodenya terlihat seperti ini:
module.exports = { firstValue: null, sort: function(list) { let sorted = list.sort(); this.firstValue = sorted[0] } }
Kali ini kami secara bersamaan memecahkan dua masalah. Pertama, kami memuat beranda google.com, dan kedua, kami mengurutkan susunan jutaan angka yang dibuat secara acak. Ini bisa memakan waktu beberapa detik, yang memberi kita peluang besar untuk melihat mekanisme Node.js baru dalam aksi. Selain itu, di sini kami mengukur waktu yang diperlukan untuk utas pekerja untuk mengurutkan angka, setelah itu kami mengirim hasil pengukuran (bersama-sama dengan elemen pertama dari array yang diurutkan) ke aliran utama, yang menampilkan hasil di konsol.
Hasil dari contoh keduaDalam contoh ini, hal yang paling penting adalah menunjukkan mekanisme pertukaran data di antara utas.
Pekerja dapat menerima pesan dari utas utama berkat metode
on
. Dalam kode Anda dapat menemukan acara yang kami dengarkan. Acara
message
setiap kali kami mengirim pesan dari utas tertentu menggunakan metode
parentPort.postMessage
. Selain itu, metode yang sama dapat digunakan untuk mengirim pesan ke utas dengan mengakses instance pekerja dan menerimanya menggunakan objek
parentPort
.
Sekarang mari kita lihat contoh lain, sangat mirip dengan apa yang telah kita lihat, tetapi kali ini kita akan memberikan perhatian khusus pada struktur proyek.
Contoh No. 3
Sebagai contoh terakhir, kami mengusulkan untuk mempertimbangkan penerapan fungsi yang sama seperti pada contoh sebelumnya, tetapi kali ini kami akan memperbaiki struktur kode, menjadikannya lebih bersih, membawanya ke bentuk yang meningkatkan kenyamanan mendukung proyek perangkat lunak.
Ini adalah kode untuk program utama.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); const request = require("request"); function startWorker(path, cb) { let w = new Worker(path, {workerData: null}); w.on('message', (msg) => { cb(null, msg) }) w.on('error', cb); w.on('exit', (code) => { if(code != 0) console.error(new Error(`Worker stopped with exit code ${code}`)) }); return w; } console.log("this is the main thread") let myWorker = startWorker(__dirname + '/workerCode.js', (err, result) => { if(err) return console.error(err); console.log("[[Heavy computation function finished]]") console.log("First value is: ", result.val); console.log("Took: ", (result.timeDiff / 1000), " seconds"); }) const start = Date.now(); request.get('http://www.google.com', (err, resp) => { if(err) { return console.error(err); } console.log("Total bytes received: ", resp.body.length); //myWorker.postMessage({finished: true, timeDiff: Date.now() - start}) // })
Dan di sini adalah kode yang menggambarkan perilaku utas pekerja (dalam program di atas, jalur ke file dengan kode ini dibentuk menggunakan
__dirname + '/workerCode.js'
):
const { parentPort } = require('worker_threads'); function random(min, max) { return Math.random() * (max - min) + min } const sorter = require("./list-sorter"); const start = Date.now() let bigList = Array(1000000).fill().map( (_) => random(1,10000)) sorter.sort(bigList); parentPort.postMessage({ val: sorter.firstValue, timeDiff: Date.now() - start});
Berikut adalah fitur dari contoh ini:
- Sekarang kode untuk utas utama dan utas pekerja terletak di file yang berbeda. Ini memfasilitasi dukungan dan perluasan proyek.
- Fungsi
startWorker
mengembalikan contoh baru pekerja, yang memungkinkan, jika perlu, untuk mengirim pesan ke pekerja ini dari aliran utama. - Tidak perlu memeriksa apakah kode dieksekusi di utas utama (kami menghapus
if
dengan cek yang sesuai). - Pekerja menunjukkan fragmen kode berkomentar yang menunjukkan mekanisme untuk menerima pesan dari aliran utama, yang, mengingat mekanisme pengiriman pesan yang sudah dibahas, memungkinkan pertukaran data dua arah yang tidak sinkron antara aliran utama dan aliran pekerja.
Ringkasan
Dalam artikel ini, kami, menggunakan contoh-contoh praktis, memeriksa fitur menggunakan kemampuan baru untuk bekerja dengan aliran di Node.js. Jika Anda sudah menguasai apa yang dibahas di sini, itu berarti Anda siap untuk melihat dokumentasi Anda dan memulai percobaan Anda sendiri dengan modul
worker_threads
. Mungkin perlu dicatat bahwa fitur ini hanya muncul di Node.js, sementara ini bersifat eksperimental, jadi seiring waktu, sesuatu dalam implementasinya dapat berubah. Selain itu, jika selama percobaan Anda sendiri dengan
worker_threads
Anda menemukan kesalahan, atau menemukan bahwa modul ini tidak mengganggu beberapa fitur yang hilang darinya, biarkan pengembang tahu tentang hal itu dan membantu meningkatkan platform Node.js.
Pembaca yang budiman! Apa pendapat Anda tentang dukungan multithreading di Node.js? Apakah Anda berencana untuk menggunakan fitur ini dalam proyek Anda?