Thứ Năm, 15 tháng 4, 2021

Xử lý đa luồng trong NodeJS




Trong một vài trường hợp một số xử lý rất tốn tài nguyên và mất rất nhiều thời gian để xử lý.

db.findAll('SELECT ...', function(err, results) {
if (err) return console.error(err)
// Heavy computation and many results
for (const encrypted of results) {
const plainText = decrypt(encrypted)
console.log(plainText)
}
})

Node JS luôn chỉ mở một thread để handle all request.
Nên nếu một xử lý quá nặng và mất thời gian, có thể làm đóng băng cả hệ thống.
==> Các xử lý đều cần phải xử lý bất đồng bộ, để không bị mắc kẹt ở một event nào.

#### Cách 1: chia nhỏ một event có xử lý quá nặng thành các phần nhỏ.
Xử lý từng phần ở hàng đợi
mỗi lần xử lý 10 item và gọi `setImmediate(callback)`
vì vậy nếu có việc gì khác mà chương trình cần phải làm,
nó sẽ được thực hiện chen giữa các khối 10 item.

const crypto = require('crypto')

const arr = new Array(200).fill('something')
function processChunk() {
if (arr.length === 0) {
// code that runs after the whole array is executed
} else {
console.log('processing chunk');
// pick 10 items and remove them from the array
const subarr = arr.splice(0, 10)
for (const item of subarr) {
// do heavy stuff for each item on the array
doHeavyStuff(item)
}
// Đưa function xử lý vào lại hàng đợi
setImmediate(processChunk)
}
}

processChunk()

function doHeavyStuff(item) {
crypto.createHmac('sha256', 'secret')
            .update(new Array(10000).fill(item).join('.')).digest('hex')
}

// This is just for confirming that we can continue
// doing things
let interval = setInterval(() => {
console.log('tick!')
if (arr.length === 0) clearInterval(interval)
}, 0)

Tuy nhiên cách này dữ liệu được chia nhỏ xử lý từng phần
và sẽ bị chen ngang rất nhiều lần dẫn đến thời gian để người gửi request nhận
được kết quả là rất lâu.

#### Cách 2: Dùng fork process (background process) - worker-farm module
Đưa function xử lý nặng vào file script.js.
Khởi chạy một task với input, mà có thể sử dụng bất cứ lượng CPU nào và thời gian nó cần,
và sau đó trả lại kết quả cho main app.

// main app
const workerFarm = require('worker-farm')

// Tạo 1 worker xử lý riêng ở script.js
const service = workerFarm(require.resolve('./script'))
service('hello', function (err, output) {
console.log(output)
})

// script.js
// This will run in forked processes
module.exports = (input, callback) => {
callback(null, input + ' ' + world)
}

Vấn đề được giải quyết, nhưng ta vẫn sử dụng rất nhiều bộ nhớ hơn thay vì multithreading.
Thread vẫn rất là gọn nhẹ so với forked process.
Và đây là lý do `Worker Thread` được sinh ra.

#### Cách 3: sử dụng Worker Thread
https://nodejs.org/api/worker_threads.html

// File index.js : run with `node --experimental-worker index.js` on Node.js 10.x

// import worker_threads module
const { Worker } = require('worker_threads')

// khởi tạo worker từ file service.js,
    // truyền dữ liệu vào và chờ kết quả sau khi service.js xử lý xong
function runService(workerData) {
return new Promise((resolve, reject) => {
// tạo mới worker từ file service.js
const worker = new Worker('./service.js', { workerData });

worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
})
})
}

// tạo function bất đồng bộ để xử lý service
async function run() {
const result = await runService('world')
console.log(result);
}

// kích hoạt Worker Thread
run().catch(err => console.error(err))


// File service.js
const { workerData, parentPort } = require('worker_threads')

// You can do any heavy stuff here, in a synchronous way
// without blocking the "main thread"
// Sau khi xử lý xong, gửi message về, kèm theo dữ liệu kết quả.
parentPort.postMessage({ hello: workerData })


Không có nhận xét nào:

Đăng nhận xét

Học lập trình web căn bản với PHP

Bài 1: Các kiến thức căn bản Part 1:  https://jimmyvan88.blogspot.com/2012/05/can-ban-lap-trinh-web-voi-php-bai-1-cac.html Part 2:  https://...