Thứ sáu, 31/07/2020 | 00:00 GMT+7

Cách khởi chạy các tiến trình con trong Node.js

Khi user thực thi một chương trình Node.js đơn lẻ, chương trình đó sẽ chạy như một quy trình hệ điều hành (OS) duy nhất đại diện cho version của chương trình đang chạy. Trong quá trình đó, Node.js thực thi các chương trình trên một stream duy nhất. Như đã đề cập trước đó trong loạt bài này với hướng dẫn Cách viết mã không đồng bộ trong Node.js , bởi vì chỉ có một stream có thể chạy trên một quy trình, các hoạt động mất nhiều thời gian để thực thi trong JavaScript có thể chặn stream Node.js và làm chậm quá trình thực thi. của mã khác. Một chiến lược quan trọng để giải quyết vấn đề này là chạy một quy trình con , hoặc một quy trình được tạo bởi một quy trình khác, khi phải đối mặt với các nhiệm vụ kéo dài. Khi một quy trình mới được chạy , hệ điều hành có thể sử dụng các kỹ thuật đa xử lý đảm bảo rằng quy trình Node.js chính và quy trình con bổ sung chạy đồng thời hoặc cùng một lúc.

Node.js bao gồm mô-đun child_process , có chức năng tạo các quy trình mới. Ngoài việc xử lý các việc lâu dài, module này cũng có thể giao tiếp với hệ điều hành và chạy các lệnh shell . Người quản trị hệ thống có thể sử dụng Node.js để chạy các lệnh shell nhằm cấu trúc và duy trì hoạt động của họ như một mô-đun Node.js thay vì cácscript shell .

Trong hướng dẫn này, bạn sẽ tạo các quy trình con trong khi thực thi một loạt các ứng dụng Node.js mẫu. Bạn sẽ tạo các quy trình với module child_process bằng cách truy xuất kết quả của một quy trình con thông qua cache hoặc chuỗi với hàm thi exec() và sau đó từ một stream dữ liệu với hàm spawn() . Bạn sẽ hoàn thành bằng cách sử dụng fork() để tạo một tiến trình con của một chương trình Node.js khác mà bạn có thể giao tiếp khi nó đang chạy. Để minh họa các khái niệm này, bạn sẽ viết một chương trình liệt kê nội dung của một folder , một chương trình để tìm file và một web server với nhiều điểm cuối.

Yêu cầu

Bước 1 - Tạo Quy trình con với exec()

Các nhà phát triển thường tạo các quy trình con để thực thi các lệnh trên hệ điều hành của họ khi họ cần thao tác kết quả của các chương trình Node.js của họ với một shell , chẳng hạn như sử dụng đường ống hoặc chuyển hướng shell . Hàm exec() trong Node.js tạo một quy trình shell mới và thực thi một lệnh trong shell đó. Đầu ra của lệnh được giữ trong một cache trong bộ nhớ, mà bạn có thể chấp nhận thông qua một hàm gọi lại được truyền vào exec() .

Hãy bắt đầu tạo các quy trình con đầu tiên của ta trong Node.js. Đầu tiên, ta cần cài đặt môi trường mã hóa của bạn để lưu trữ các script mà ta sẽ tạo trong suốt hướng dẫn này. Trong terminal , tạo một folder có tên là child-processes :

  • mkdir child-processes

Nhập folder đó vào terminal bằng lệnh cd :

  • cd child-processes

Tạo một file mới có tên listFiles.js và mở file trong editor . Trong hướng dẫn này, ta sẽ sử dụng nano , một editor terminal :

  • nano listFiles.js

Ta sẽ viết một module Node.js sử dụng hàm exec() để chạy ls . Lệnh ls liệt kê các file và folder trong một folder . Chương trình này lấy kết quả từ ls và hiển thị cho user .

Trong editor , hãy thêm mã sau:

~ / child-process / listFiles.js
const { exec } = require('child_process');  exec('ls -lh', (error, stdout, stderr) => {   if (error) {     console.error(`error: ${error.message}`);     return;   }    if (stderr) {     console.error(`stderr: ${stderr}`);     return;   }    console.log(`stdout:\n${stdout}`); }); 

Đầu tiên, ta nhập lệnh child_process exec() từ module child_process bằng cách sử dụng cấu trúc JavaScript . Sau khi được nhập, ta sử dụng hàm exec() . Đối số đầu tiên là lệnh mà ta muốn chạy. Trong trường hợp này, đó là ls -lh , liệt kê tất cả các file và folder trong folder hiện tại ở định dạng dài, với tổng kích thước file tính bằng đơn vị con người có thể đọc được ở đầu kết quả .

Đối số thứ hai là một hàm gọi lại với ba tham số: error , stdoutstderr . Nếu lệnh không chạy được, error sẽ ghi lại lý do tại sao lệnh không thành công. Điều này có thể xảy ra nếu shell không thể tìm thấy lệnh bạn đang cố gắng thực thi. Nếu lệnh được thực thi thành công, bất kỳ dữ liệu nào nó ghi vào luồng kết quả tiêu chuẩn sẽ được ghi lại trong stdout và mọi dữ liệu mà nó ghi vào luồng lỗi tiêu chuẩn sẽ được ghi lại trong stderr .

Lưu ý: Điều quan trọng là phải ghi nhớ sự khác biệt giữa errorstderr . Nếu bản thân lệnh không chạy, error sẽ bắt lỗi. Nếu lệnh chạy nhưng trả về kết quả kết quả cho stream lỗi, stderr sẽ nắm bắt nó. Các chương trình Node.js linh hoạt nhất sẽ xử lý tất cả các kết quả có thể có cho một quy trình con.

Trong chức năng gọi lại của ta , trước tiên ta kiểm tra xem ta có gặp lỗi không. Nếu ta đã làm, ta hiển thị của lỗi message (một tài sản của Error đối tượng) với console.error() và kết thúc chức năng với return . Sau đó, ta kiểm tra xem lệnh có in thông báo lỗi không và return nếu có. Nếu lệnh thực thi thành công, ta ghi kết quả của nó vào control panel bằng console.log() .

Hãy chạy file này để xem nó hoạt động. Đầu tiên, lưu và thoát nano bằng cách nhấn CTRL+X

Quay lại terminal của bạn, chạy ứng dụng của bạn bằng lệnh node :

  • node listFiles.js

Thiết bị terminal của bạn sẽ hiển thị kết quả sau:

Output
stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

Phần này liệt kê nội dung của folder child-processes ở định dạng dài, cùng với kích thước của nội dung ở trên cùng. Kết quả của bạn sẽ có user và group của bạn thay cho sammy . Điều này cho thấy rằng chương trình listFiles.js đã chạy thành công lệnh shell ls -lh .

Bây giờ ta hãy xem xét một cách khác để thực thi các quy trình đồng thời. Mô-đun child_process của Node.js cũng có thể chạy các file thực thi với hàm execFile() . Sự khác biệt chính giữa các execFile() và thực thi exec() là đối số đầu tiên của thực execFile() bây giờ là một đường dẫn đến một file thực thi thay vì một lệnh. Đầu ra của file thực thi được lưu trữ trong một cache như exec() , mà ta truy cập thông qua hàm gọi lại với các tham số error , stdoutstderr .

Lưu ý: Không thể chạy các tập lệnh trong Windows như file .bat.cmd với execFile() vì hàm không tạo shell khi chạy file . Trên Unix, Linux và macOS, các tập lệnh thực thi không phải lúc nào cũng cần shell để chạy. Tuy nhiên, máy Windows cần một shell để thực thi các tập lệnh. Để thực thi các file kịch bản trên Windows, hãy sử dụng file execute exec() , vì nó tạo ra một shell mới. Ngoài ra, bạn có thể sử dụng spawn() , mà bạn sẽ sử dụng sau trong Bước này.

Tuy nhiên, lưu ý bạn có thể thực thi các file .exe trong Windows thành công bằng cách sử dụng execFile() . Hạn chế này chỉ áp dụng cho các file kịch bản yêu cầu shell để thực thi.

Hãy bắt đầu bằng cách thêm một tập lệnh thực thi để execFile() . Ta sẽ viết một tập lệnh bash download biểu trưng Node.js từ trang web Node.js và Base64 mã hóa nó để chuyển đổi dữ liệu của nó thành một chuỗi ký tự ASCII .

Tạo một file script shell mới có tên processNodejsImage.sh :

  • nano processNodejsImage.sh

Bây giờ hãy viết một tập lệnh để download hình ảnh và base64 chuyển đổi nó:

~ / child-process / processNodejsImage.sh
#!/bin/bash curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg base64 nodejs-logo.svg 

Tuyên bố đầu tiên là một tuyên bố shebang . Nó được sử dụng trong Unix, Linux và macOS khi ta muốn chỉ định một shell để thực thi tập lệnh của bạn . Câu lệnh thứ hai là một lệnh curl . Tiện ích cURL , có lệnh là curl , là một công cụ dòng lệnh có thể truyền dữ liệu đến và đi từ một server . Ta sử dụng cURL để download biểu trưng Node.js từ trang web, sau đó ta sử dụngchuyển hướng để lưu dữ liệu đã download vào file mới nodejs-logo.svg . Câu lệnh cuối cùng sử dụng trình base64 để mã hóa file nodejs-logo.svg mà ta đã download bằng cURL. Sau đó, tập lệnh xuất chuỗi được mã hóa đến console .

Lưu và thoát trước khi tiếp tục.

Để chương trình Node của ta chạy tập lệnh bash, ta phải làm cho nó có thể thực thi được. Để làm điều này, hãy chạy như sau:

  • chmod u+x processNodejsImage.sh

Điều này sẽ cung cấp cho user hiện tại của bạn quyền thực thi file .

Với tập lệnh của ta tại chỗ, ta có thể viết một module Node.js mới để thực thi nó. Tập lệnh này sẽ sử dụng execFile() để chạy tập lệnh trong một tiến trình con, bắt bất kỳ lỗi nào và hiển thị tất cả kết quả cho console .

Trong terminal của bạn, hãy tạo một file JavaScript mới có tên getNodejsImage.js :

  • nano getNodejsImage.js

Nhập mã sau vào editor :

~ / child-process / getNodejsImage.js
const { execFile } = require('child_process');  execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {   if (error) {     console.error(`error: ${error.message}`);     return;   }    if (stderr) {     console.error(`stderr: ${stderr}`);     return;   }    console.log(`stdout:\n${stdout}`); }); 

Ta sử dụng hàm hủy JavaScript để nhập hàm execFile() từ module child_process . Sau đó, ta sử dụng chức năng đó, chuyển đường dẫn file làm tên đầu tiên. __dirname chứa đường dẫn folder của module mà nó được viết. Node.js cung cấp biến __dirname cho một module khi module chạy. Bằng cách sử dụng __dirname , kịch bản của ta sẽ luôn tìm ra processNodejsImage.sh file trên hệ điều hành khác nhau, không có vấn đề mà ta chạy getNodejsImage.js . Lưu ý đối cài đặt dự án hiện tại, ta getNodejsImage.jsprocessNodejsImage.sh phải nằm trong cùng một folder .

Đối số thứ hai là một lệnh gọi lại với các tham error , stdoutstderr . Giống như với ví dụ trước của ta đã sử dụng exec() , ta kiểm tra từng kết quả có thể có của file script và đăng nhập chúng vào console .

Trong editor của bạn, hãy lưu file này và thoát khỏi editor .

Trong terminal của bạn, sử dụng node để thực thi module :

  • node getNodejsImage.js

Chạy tập lệnh này sẽ tạo ra kết quả như sau:

Output
stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

Lưu ý ta đã cắt bớt kết quả trong bài viết này vì kích thước lớn của nó.

Trước khi mã hóa base64 hình ảnh, trước tiên processNodejsImage.sh tải nó xuống. Bạn cũng có thể xác minh bạn đã download hình ảnh bằng cách kiểm tra folder hiện tại.

Thực thi listFiles.js để tìm danh sách các file được cập nhật trong folder của ta :

  • node listFiles.js

Tập lệnh sẽ hiển thị nội dung tương tự như sau trên terminal :

Output
stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

Bây giờ ta đã thực thi thành công processNodejsImage.sh dưới dạng một quy trình con trong Node.js bằng cách sử dụng hàm execFile() .

Các execFile() exec()execFile() có thể chạy các lệnh trên shell của hệ điều hành trong một tiến trình con Node.js. Node.js cũng cung cấp một phương thức khác có chức năng tương tự, spawn() . Sự khác biệt là thay vì nhận kết quả của tất cả các lệnh shell cùng một lúc, ta lấy chúng theo từng phần thông qua một stream . Trong phần tiếp theo, ta sẽ sử dụng lệnh spawn() để tạo một tiến trình con.

Bước 2 - Tạo Quy trình con với spawn()

Hàm spawn() chạy một lệnh trong một tiến trình. Hàm này trả về dữ liệu thông qua API stream . Do đó, để có được kết quả của tiến trình con, ta cần lắng nghe các sự kiện stream .

Các stream trong Node.js là các thể hiện của bộ phát sự kiện. Nếu bạn muốn tìm hiểu thêm về cách lắng nghe các sự kiện và nền tảng của việc tương tác với các stream , bạn có thể đọc hướng dẫn của ta về Sử dụng Bộ phát sự kiện trong Node.js.

Nó thường là một ý tưởng tốt để chọn spawn() trên exec() hoặc execFile() khi lệnh bạn muốn chạy lon ra một lượng lớn dữ liệu. Với một cache , như được sử dụng bởi exec()execFile() , tất cả các dữ liệu xử lý được lưu trữ trong bộ nhớ của máy tính. Đối với một lượng lớn dữ liệu, điều này có thể làm giảm hiệu suất hệ thống. Với một stream , dữ liệu được xử lý và chuyển theo từng phần nhỏ. Do đó, bạn có thể xử lý một lượng lớn dữ liệu mà không cần sử dụng quá nhiều bộ nhớ cùng một lúc.

Hãy xem cách ta có thể sử dụng spawn() để tạo một quy trình con. Ta sẽ viết một module Node.js mới tạo ra một tiến trình con để chạy lệnh find . Ta sẽ sử dụng lệnh find để liệt kê tất cả các file trong folder hiện tại.

Tạo một file mới có tên findFiles.js :

  • nano findFiles.js

Trong editor của bạn, hãy bắt đầu bằng cách gọi lệnh spawn() :

~ / child-process / findFiles.js
const { spawn } = require('child_process');  const child = spawn('find', ['.']); 

Đầu tiên ta nhập hàm spawn() từ module child_process . Sau đó, ta gọi hàm spawn() để tạo một tiến trình con thực hiện lệnh find . Ta giữ tham chiếu đến tiến trình trong biến child , mà ta sẽ sử dụng để lắng nghe các sự kiện được truyền trực tuyến của nó.

Đối số đầu tiên trong spawn() là lệnh chạy, trong trường hợp này là find . Đối số thứ hai là một mảng chứa các đối số cho lệnh được thực thi. Trong trường hợp này, ta đang yêu cầu Node.js thực hiện lệnh find với đối số . , do đó thực hiện lệnh tìm tất cả các file trong folder hiện tại. Lệnh tương đương trong terminal là find . .

Với các execFile() exec()execFile() , ta đã viết các đối số cùng với lệnh trong một chuỗi. Tuy nhiên, với spawn() , tất cả các đối số của lệnh phải được nhập vào mảng. Đó là bởi vì spawn() , không giống như thi exec() và thực execFile() , không tạo một shell mới trước khi chạy một quy trình. Để có các lệnh với các đối số của chúng trong một chuỗi, bạn cũng cần Node.js để tạo một shell mới.

Hãy tiếp tục module của ta bằng cách thêm trình nghe cho kết quả của lệnh. Thêm các dòng được đánh dấu sau:

~ / child-process / findFiles.js
const { spawn } = require('child_process');  const child = spawn('find', ['.']);  child.stdout.on('data', data => {   console.log(`stdout:\n${data}`); });  child.stderr.on('data', data => {   console.error(`stderr: ${data}`); }); 

Các lệnh có thể trả về dữ liệu trong stream stdout hoặc stream stderr , vì vậy bạn đã thêm trình nghe cho cả hai. Bạn có thể thêm người nghe bằng cách gọi phương thức on() của từng đối tượng stream . Sự kiện data từ các stream cung cấp cho ta kết quả của lệnh tới stream đó. Khi nào ta nhận được dữ liệu trên một trong hai stream , ta sẽ ghi dữ liệu đó vào console .

Sau đó, ta lắng nghe hai sự kiện khác: sự kiện error nếu lệnh không thực thi hoặc bị gián đoạn và sự kiện close khi lệnh kết thúc thực hiện, do đó đóng stream .

Trong editor , hãy hoàn thành module Node.js bằng cách viết các dòng được đánh dấu sau:

~ / child-process / findFiles.js
const { spawn } = require('child_process');  const child = spawn('find', ['.']);  child.stdout.on('data', (data) => {   console.log(`stdout:\n${data}`); });  child.stderr.on('data', (data) => {   console.error(`stderr: ${data}`); });  child.on('error', (error) => {   console.error(`error: ${error.message}`); });  child.on('close', (code) => {   console.log(`child process exited with code ${code}`); }); 

Đối với sự kiện error và sự kiện close , bạn cài đặt trình lắng nghe trực tiếp trên biến child . Khi lắng nghe các sự kiện error , nếu một sự kiện xảy ra, Node.js sẽ cung cấp một đối tượng Error . Trong trường hợp này, bạn đăng nhập của lỗi message bất động sản.

Khi nghe sự kiện close , Node.js cung cấp mã thoát của lệnh. Mã thoát biểu thị nếu lệnh chạy thành công hay không. Khi một lệnh chạy mà không có lỗi, nó trả về giá trị thấp nhất có thể cho mã thoát: 0 . Khi được thực thi với lỗi, nó sẽ trả về một mã khác 0.

Mô-đun đã hoàn tất. Lưu và thoát nano bằng CTRL+X

Bây giờ, hãy chạy mã bằng lệnh node :

  • node findFiles.js

Sau khi hoàn tất, bạn sẽ tìm thấy kết quả sau:

Output
stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

Ta tìm thấy danh sách tất cả các file trong folder hiện tại của ta và mã thoát của lệnh, mã này là 0 khi nó chạy thành công. Mặc dù folder hiện tại của ta có một số lượng nhỏ các file , nếu ta chạy mã này trong folder chính của bạn , chương trình của ta sẽ liệt kê từng file trong mọi folder có thể truy cập cho user của ta . Bởi vì nó có kết quả tiềm năng lớn như vậy, sử dụng hàm spawn() là lý tưởng nhất vì các stream của nó không yêu cầu nhiều bộ nhớ như một cache lớn.

Lúc này, ta đã sử dụng các hàm để tạo các quy trình con để thực thi các lệnh bên ngoài trong hệ điều hành của ta . Node.js cũng cung cấp một cách để tạo một tiến trình con thực thi các chương trình Node.js khác. Hãy sử dụng hàm fork() để tạo một tiến trình con cho một module Node.js trong phần tiếp theo.

Bước 3 - Tạo Quy trình con với fork()

Node.js cung cấp hàm fork() , một biến thể của spawn() , để tạo một quy trình con cũng là một quy trình Node.js. Lợi ích chính của việc sử dụng fork() để tạo một quy trình Node.js qua spawn() hoặc exec()fork() cho phép giao tiếp giữa quy trình mẹ và quy trình con.

Với fork() , ngoài việc lấy dữ liệu từ tiến trình con, một tiến trình mẹ có thể gửi tin nhắn đến tiến trình con đang chạy. Tương tự như vậy, process con có thể gửi tin nhắn đến process cha.

Hãy xem một ví dụ trong đó việc sử dụng fork() để tạo một quy trình con Node.js mới có thể cải thiện hiệu suất của ứng dụng của ta . Các chương trình Node.js chạy trên một quy trình duy nhất. Do đó, các việc chuyên sâu của CPU như lặp qua các vòng lặp lớn hoặc phân tích cú pháp các tệp JSON lớn sẽ ngăn mã JavaScript khác chạy. Đối với một số ứng dụng nhất định, đây không phải là một lựa chọn khả thi. Nếu một web server bị chặn, thì nó không thể xử lý bất kỳ yêu cầu mới nào đến cho đến khi mã chặn hoàn tất việc thực thi.

Hãy xem điều này trong thực tế bằng cách tạo một web server với hai điểm cuối. Một điểm cuối sẽ thực hiện một phép tính chậm chặn quá trình Node.js. Điểm cuối kia sẽ trả về một đối tượng JSON nói lời hello .

Đầu tiên, hãy tạo một file mới có tên là httpServer.js , file này sẽ có mã cho server HTTP của ta :

  • nano httpServer.js

Ta sẽ bắt đầu bằng cách cài đặt server HTTP. Điều này liên quan đến việc nhập module http , tạo hàm lắng nghe yêu cầu, tạo đối tượng server và lắng nghe các yêu cầu trên đối tượng server . Nếu bạn muốn đi sâu hơn vào việc tạo server HTTP trong Node.js hoặc muốn cập nhật, bạn có thể đọc hướng dẫn của ta về Cách tạo web server trong Node.js bằng Mô-đun HTTP .

Nhập mã sau vào editor của bạn để cài đặt server HTTP:

~ / child-process / httpServer.js
const http = require('http');  const host = 'localhost'; const port = 8000;  const requestListener = function (req, res) {};  const server = http.createServer(requestListener); server.listen(port, host, () => {   console.log(`Server is running on http://${host}:${port}`); }); 

Mã này cài đặt một server HTTP sẽ chạy tại http://localhost:8000 . Nó sử dụng các ký tự mẫu để tạo động URL đó.

Tiếp theo, ta sẽ viết một hàm làm chậm có chủ đích đếm trong một vòng lặp 5 tỷ lần. Trước hàm requestListener() , hãy thêm mã sau:

~ / child-process / httpServer.js
... const port = 8000;  const slowFunction = () => {   let counter = 0;   while (counter < 5000000000) {     counter++;   }    return counter; }  const requestListener = function (req, res) {}; ... 

Điều này sử dụng cú pháp hàm mũi tên để tạo một vòng lặp while đếm đến 5000000000 .

Để hoàn thành module này, ta cần thêm mã vào hàm requestListener() . Hàm của ta sẽ gọi slowFunction() trên đường dẫn con và trả về một thông báo JSON nhỏ cho hàm kia. Thêm mã sau vào module :

~ / child-process / httpServer.js
... const requestListener = function (req, res) {   if (req.url === '/total') {     let slowResult = slowFunction();     let message = `{"totalCount":${slowResult}}`;      console.log('Returning /total results');     res.setHeader('Content-Type', 'application/json');     res.writeHead(200);     res.end(message);   } else if (req.url === '/hello') {     console.log('Returning /hello results');     res.setHeader('Content-Type', 'application/json');     res.writeHead(200);     res.end(`{"message":"hello"}`);   } }; ... 

Nếu user đến server tại đường dẫn con /total , thì ta chạy slowFunction() . Nếu ta gặp phải ở đường dẫn con /hello , ta sẽ trả về thông báo JSON này: {"message":"hello"} .

Lưu và thoát khỏi file bằng cách nhấn CTRL+X

Để kiểm tra, hãy chạy module server này với node :

  • node httpServer.js

Khi server của ta khởi động, console sẽ hiển thị như sau:

Output
Server is running on http://localhost:8000

Bây giờ, để kiểm tra hiệu suất của module của ta , hãy mở hai terminal bổ sung. Trong terminal đầu tiên, sử dụng lệnh curl để thực hiện yêu cầu đến điểm cuối /total , mà ta dự kiến sẽ chậm:

  • curl http://localhost:8000/total

Trong terminal khác, sử dụng curl để thực hiện yêu cầu tới điểm cuối /hello như sau:

  • curl http://localhost:8000/hello

Yêu cầu đầu tiên sẽ trả về JSON sau:

Output
{"totalCount":5000000000}

Trong khi yêu cầu thứ hai sẽ trả về JSON này:

Output
{"message":"hello"}

Yêu cầu đến /hello chỉ hoàn thành sau khi yêu cầu đến /total . slowFunction() đã chặn không cho tất cả các mã khác thực thi trong khi nó vẫn ở trong vòng lặp của nó. Bạn có thể xác minh điều này bằng cách xem kết quả server Node.js được đăng nhập trong terminal ban đầu của bạn:

Output
Returning /total results Returning /hello results

Để xử lý mã chặn trong khi vẫn chấp nhận các yêu cầu đến, ta có thể chuyển mã chặn sang một quy trình con với fork() . Ta sẽ chuyển mã chặn vào module riêng của nó. Server Node.js sau đó sẽ tạo một quy trình con khi ai đó truy cập vào /total endpoint và lắng nghe kết quả từ quy trình con này.

Khởi động lại server bằng cách tạo module mới có tên getCount.js trước tiên sẽ chứa slowFunction() :

  • nano getCount.js

Bây giờ hãy nhập lại mã cho slowFunction() :

~ / child-process / getCount.js
const slowFunction = () => {   let counter = 0;   while (counter < 5000000000) {     counter++;   }    return counter; } 

Vì module này sẽ là một quy trình con được tạo bằng fork() , ta cũng có thể thêm mã để giao tiếp với quy trình mẹ khi slowFunction() đã hoàn tất quá trình xử lý. Thêm khối mã sau để gửi thông báo đến quy trình mẹ với JSON để trả lại cho user :

~ / child-process / getCount.js
const slowFunction = () => {   let counter = 0;   while (counter < 5000000000) {     counter++;   }    return counter; }  process.on('message', (message) => {   if (message == 'START') {     console.log('Child process received START message');     let slowResult = slowFunction();     let message = `{"totalCount":${slowResult}}`;     process.send(message);   } }); 

Hãy chia nhỏ khối mã này. Các thông báo giữa một tiến trình mẹ và con được tạo bởi fork() có thể truy cập được thông qua đối tượng process global Node.js. Ta thêm trình lắng nghe vào biến process để tìm kiếm các sự kiện message . Khi ta nhận được một sự kiện message , ta sẽ kiểm tra xem đó có phải là sự kiện START . Mã server của ta sẽ gửi sự kiện START khi ai đó truy cập vào /total endpoint. Khi nhận được sự kiện đó, ta chạy slowFunction() và tạo một chuỗi JSON với kết quả của hàm. Ta sử dụng process.send() để gửi một thông báo đến tiến trình mẹ.

Lưu và thoát getCount.js bằng lệnh CTRL+X trong nano.

Bây giờ, hãy sửa đổi file httpServer.js để thay vì gọi slowFunction() , nó tạo ra một tiến trình con thực thi getCount.js .

Mở lại httpServer.js bằng nano :

  • nano httpServer.js

Đầu tiên, nhập hàm fork() từ module child_process :

~ / child-process / httpServer.js
const http = require('http'); const { fork } = require('child_process'); ... 

Tiếp theo, ta sẽ xóa slowFunction() khỏi module này và sửa đổi hàm requestListener() để tạo một tiến trình con. Thay đổi mã trong file của bạn để nó trông giống như sau:

~ / child-process / httpServer.js
... const port = 8000;  const requestListener = function (req, res) {   if (req.url === '/total') {     const child = fork(__dirname + '/getCount');      child.on('message', (message) => {       console.log('Returning /total results');       res.setHeader('Content-Type', 'application/json');       res.writeHead(200);       res.end(message);     });      child.send('START');   } else if (req.url === '/hello') {     console.log('Returning /hello results');     res.setHeader('Content-Type', 'application/json');     res.writeHead(200);     res.end(`{"message":"hello"}`);   } }; ... 

Khi ai đó đi đến /total endpoint, bây giờ ta tạo một process con mới với fork() . Đối số của fork() là đường dẫn đến module Node.js. Trong trường hợp này, đó là file getCount.js trong folder hiện tại của ta , mà ta nhận được từ __dirname . Tham chiếu đến tiến trình con này được lưu trữ trong một biến child .

Sau đó, ta thêm một trình lắng nghe vào đối tượng child . Trình lắng nghe này nắm bắt bất kỳ thông điệp nào mà tiến trình con cung cấp cho ta . Trong trường hợp này, getCount.js sẽ trả về một chuỗi JSON với tổng số đếm bởi while vòng lặp. Khi ta nhận được thông báo đó, ta sẽ gửi JSON cho user .

Ta sử dụng hàm send() của biến child để cung cấp cho nó một thông báo. Chương trình này sẽ gửi thông báo START , bắt đầu quá trình thực thi slowFunction() trong tiến trình con.

Lưu và thoát nano bằng lệnh CTRL+X

Để kiểm tra sự cải tiến bằng cách sử dụng fork() được thực hiện trên server HTTP, hãy bắt đầu bằng cách thực thi file httpServer.js với node :

  • node httpServer.js

Giống như trước đây, nó sẽ xuất ra thông báo sau khi chạy :

Output
Server is running on http://localhost:8000

Để kiểm tra server , ta cần thêm hai terminal như lần đầu tiên. Bạn có thể sử dụng lại chúng nếu chúng vẫn còn mở.

Trong terminal đầu tiên, sử dụng lệnh curl để thực hiện yêu cầu đến điểm cuối /total , quá trình này sẽ mất một lúc để tính toán:

  • curl http://localhost:8000/total

Trong terminal khác, sử dụng curl để đưa ra yêu cầu tới điểm cuối /hello , điểm này sẽ phản hồi trong thời gian ngắn:

  • curl http://localhost:8000/hello

Yêu cầu đầu tiên sẽ trả về JSON sau:

Output
{"totalCount":5000000000}

Trong khi yêu cầu thứ hai sẽ trả về JSON này:

Output
{"message":"hello"}

Không giống như lần đầu tiên ta thử điều này, yêu cầu thứ hai tới /hello chạy ngay lập tức. Bạn có thể xác nhận bằng cách xem lại log , log sẽ giống như sau:

Output
Child process received START message Returning /hello results Returning /total results

Các bản ghi này cho thấy rằng yêu cầu cho điểm cuối /hello chạy sau khi tiến trình con được tạo nhưng trước khi tiến trình con hoàn thành nhiệm vụ của nó.

Vì ta đã di chuyển mã chặn trong quy trình con sử dụng fork() , server vẫn có thể phản hồi các yêu cầu khác và thực thi mã JavaScript khác. Do khả năng truyền thông điệp của hàm fork() , ta có thể kiểm soát khi nào một tiến trình con bắt đầu một hoạt động và ta có thể trả về dữ liệu từ một tiến trình con cho một tiến trình mẹ.

Kết luận

Trong bài viết này, bạn đã sử dụng các hàm khác nhau để tạo một tiến trình con trong Node.js. Đầu tiên, bạn đã tạo các quy trình con với exec() để chạy các lệnh shell từ mã Node.js. Sau đó, bạn chạy một file thực thi với hàm execFile() . Bạn nhìn vào spawn() chức năng, mà cũng có thể chạy lệnh nhưng dữ liệu lợi nhuận qua một dòng suối và không bắt đầu một vỏ như exec()execFile() . Cuối cùng, bạn đã sử dụng hàm fork() để cho phép giao tiếp hai chiều giữa tiến trình cha và con.

Để tìm hiểu thêm về module child_process , bạn có thể đọc tài liệu Node.js. Nếu bạn muốn tiếp tục học Node.js, bạn có thể quay lại loạt bài Cách viết mã trong Node.js hoặc duyệt qua các dự án lập trình và cài đặt trên trang chủ đề Node của ta .


Tags:

Các tin liên quan