Hot: Node.js có khả năng thực hiện đa luồng từ phiên bản 13.

 

Giới thiệu

Hầu hết các nhà phát triển JavaScript trước nay vẫn cho rằng Node.js là luồng đơn - single thread, xử lý nhiều hoạt động bằng các quy trình không đồng bộ và không hỗ trợ đa luồng. Giờ thì điều đó xưa rồi, vì trên phiên bản 13 của Node.js, có một mô-đun mới được gọi là “worker thread” để triển khai đa luồng.


Mặc dù lệnh gọi không đồng bộ, không chặn có thể xử lý nhiều hoạt động rất hiệu quả, nhưng các chức năng yêu cầu sử dụng dung lượng CPU lớn - ví dụ như encryption (mã hóa) sẽ làm chậm các quy trình khác, hiệu suất của Node.js là yếu đối với trường hợp như vậy. Mô-đun “worker thread” khắc phục điểm yếu đó bằng cách cô lập chức năng sử dụng CPU dung lượng cao thành một luồng riêng biệt, xử lý nó ở chế độ nền và như vậy sẽ không làm nghẽn bất kỳ quy trình nào khác.

Thực hiện

Thông thường trong Node.js, luồng chính xử lý tất cả các hoạt động. Ví dụ dưới đây sẽ trình bày cách tạo thêm một luồng khác để xử lý. Ví dụ này có hai API, API đầu tiên sẽ xử lý chức năng trên luồng chính và API thứ 2 sẽ xử lý chức năng trên một luồng riêng biệt. Đoạn mã dưới đây hiển thị cấu trúc cơ bản của ví dụ.

/*
*  File Name: index.js
*  Description: This is the main thread
*/
const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("Process function on main thread.");
});
app.get("/seprate-thread", (req, res) => {
res.send("Process function on seprate thread.");
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

Bước đầu tiên, ta thêm một function trên luồng chính. Tiếp theo, ta thêm function tương tự trên một luồng khác. Function được sử dụng sẽ là getSum, function này sẽ trả về tổng tích lũy thành giá trị giới hạn được đưa ra dưới dạng đối số. Sau khi thêm hàm getSum vào luồng chính, đoạn mã sẽ giống như bên dưới.

/*
*  File Name: index.js
*  Description: This is the main thread
*/
const express = require("express");
const app = express();
const port = 3000;
const getSum = (limit) => {
let sum = 0;
for (let i = 0; i < limit; i++) {
     sum += i;
}
return sum;
};
app.get("/", (req, res) => {
const result = getSum(1000);
res.send(`Processed function getSum on main thread and result: ${result}`);
});
app.get("/seprate-thread", (req, res) => {
res.send("Process function getSum on seprate thread.");
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

Bước tiếp theo là thêm chức năng tương tự trên một luồng khác và nó có thể thực hiện như sau.

  • Nhập mô-đun worker thread vào luồng chính.
const { Worker } = require("worker_threads");
  • Tạo một tệp khác, seprateThread.js, để xác định hàm getSum sẽ chạy trên một luồng khác.
  • Tạo một phiên bản của mô-đun worker thread và cung cấp tên đường dẫn đến tệp mới được tạo
const seprateThread = new Worker(__dirname + "/seprateThread.js");
  • Bắt đầu một luồng mới
seprateThread.on("message", (result) => {
res.send(`Processed function getSum on seprate thread:  ${result}`);
});
  • Gửi dữ liệu đến luồng mới
seprateThread.postMessage(1000);
  • Cuối cùng, luồng chính sẽ giống như đoạn mã dưới đây
/*
*  File Name: index.js
*  Description: This is the main thread
*/
const express = require("express");
const { Worker } = require("worker_threads");
const app = express();
const port = 3000;
const getSum = (limit) => {
let sum = 0;
for (let i = 0; i < limit; i++) {
     sum += i;
}
return sum;
};
app.get("/", (req, res) => {
const result = getSum(1000);
res.send(`Processed function getSum on main thread and result: ${result}`);
});
app.get("/seprate-thread", (req, res) => {
const seprateThread = new Worker(__dirname + "/seprateThread.js");
seprateThread.on("message", (result) => {
res.send(`Processed function getSum on seprate thread: ${result}`);
});
seprateThread.postMessage(1000);
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

Do đó, một luồng mới được tạo ra từ luồng chính. Chúng ta hãy đặt hàm getSum trên luồng mới được tạo, vì vậy hãy xác định hàm đó trên tệp seprateThread.js. Sau khi xác định, luồng mới sẽ gửi kết quả trở lại luồng chính; kiểm tra mã dưới đây để tham khảo.

/*
*  File Name: seprateThread.js
*  Description: This is another thread
*/
const { parentPort } = require("worker_threads");
const getSum = (limit) => {
  let sum = 0;
  for (let i = 0; i < limit; i++) {
    sum += i;
  }
  return sum;
};
parentPort.on("message", (limit) => {
 const result = getSum(limit);
 parentPort.postMessage(result);
});

Trong ví dụ trên, bạn có thể thấy hàm seprateThread.postMessage () được sử dụng bởi luồng chính để giao tiếp với luồng con. Tương tự như vậy, parentPort.postMessage () được sử dụng bởi luồng con để giao tiếp với luồng chính. Hình dưới đây minh họa giao tiếp giữa con và luồng chính

Đặc trưng

  • Mỗi luồng có các V8 engine riêng biệt.
  • Các luồng con có thể giao tiếp với nhau.
  • Các chủ đề con có thể chia sẻ cùng một bộ nhớ.
  • Giá trị ban đầu có thể được chuyển như một tùy chọn trong khi bắt đầu luồng mới.

Kết luận

Động cơ của bài viết này là đưa ra một ý tưởng ngắn gọn về cách triển khai cơ bản của đa luồng trên Node.js. Đa luồng trong Node.js hơi khác một chút so với đa luồng truyền thống. Người ta khuyên rằng đối với hoạt động I / O lớn, luồng chính có thể hoạt động tốt hơn nhiều so với worker thread. Để hiểu thêm về đa luồng, hãy tham khảo tài liệu chính thức của Node.js và mã nguồn của ví dụ có sẵn tại đây.

 

Bài dịch từ PlainEnglish

Tham khảo khoá Lộ trình Node.js 4.5 tháng của Techmaster