Child process trong Node.js là gì? - Khi nào cần sử dụng fork và spawn?

Child process trong Node.js là gì? - Khi nào cần sử dụng fork và spawn?

Tin ngắn hàng ngày dành cho bạn
  • Manus đã chính thức mở cửa cho tất cả người dùng rồi đấy mọi người. Cho những ai chưa biết thì đây là một công cụ viết báo cáo (làm mưa làm gió) giống như Deep Research của OpenAI á. Mỗi ngày được miễn phí 300 Credits để nghiên cứu. Mỗi lượt nghiên cứu tiêu tốn tuỳ thuộc vào độ phức tạp của yêu cầu. À với cả họ đang có chương trình tặng miễn phí Credits hay sao á. Như mình thì vào thấy được hẳn 2000.

    Mình dùng thử, so sánh với cùng một lệnh giống như đợt trước dùng bên Deep Research thì nội dung khác biệt nhau hoàn toàn. Manus báo cáo như kiểu viết văn hơn so với OpenAI là các gạch đầu dòng và bảng biểu.

    À lúc đăng ký xong có bắt nhập số điện thoại để xác minh, nếu lỗi thì các bạn đợi qua ngày thử lại xem có được không nhé.

    » Xem thêm
  • Mọi người chắc nghe nhiều về xu hướng tìm kiếm thông tin bằng AI chứ không cần công cụ tìm kiếm như Google nữa rồi đúng không? Không đâu xa ánh xạ vào bản thân thì thấy đúng thật, thi thoảng mới tìm kiếm thôi chứ còn đâu toàn hỏi tụi AI.

    Ngay từ đầu viết blog, thứ mà mình hướng đến là chia sẻ kinh nghiệm chứ không phải là những bài mang nặng tính kỹ thuật, máy móc, hướng dẫn từ đầu... Vì thời điểm đó đã có quá nhiều người làm nội dung này rồi và họ làm rất tốt, tại sao mình phải cố phát minh lại bánh xe? Một điều nữa là tin tưởng độc giả của mình có khả năng tìm hiểu vấn đề. Nếu bạn đọc đủ nhiều các bài viết trên blog thì thấy mình luôn cố gắng chèn thêm các liên kết tham khảo ngoài bài viết, nêu ra vấn đề mở và rất ít khi kết luận chắc chắn một điều gì đó.

    Mình đã cố gắng rèn luyện kỹ năng viết, kỹ năng trình bày và cả cách tương tác với độc giả để mang lại giá trị cho họ. Nhiều lúc ngồi lật lại các con số thống kê thấy lượng đọc bài viết tăng lên lại cảm thấy vui. Nhưng khi nguồn truy cập đến từ Google thì lại thấy buồn, vì điều đó chứng tỏ họ biết đến mình chỉ khi đang cố đi tìm giải pháp, có thể họ chỉ đọc chớp nhoáng, may ra tìm được cách giải quyết và thế là đóng cửa sổ trình duyệt rồi đi như một cơn gió.

    Chừng vài tháng đổ lại đây, một điều khiến mình rất vui đó là lượng người truy cập thẳng vào trang chủ mà không thông qua công cụ tìm kiếm đang tăng dần lên, có nhiều hôm lượng truy cập tự nhiên còn cao hơn cả đến từ Google. Điều đó chứng tỏ độc giả đã có thói quen quay lại trang của mình nhiều hơn và họ tìm thấy được giá trị từ blog mang lại. Vui mừng khôn xiết 🤩

    Bên cạnh đó thì lượng truy cập vào chuyên mục Threads - tức là mục mình đang viết bài này đang cao hơn bao giờ hết. Điều đó chứng tỏ xu hướng đi theo tin nhanh là đúng đắn. Mình có thể ngồi cả ngày để viết tin ngắn cho bạn đọc vì nó rất nhanh mà tiện, không tốn công đi tìm tài liệu để viết, không tốn cả thời gian viết nữa, còn mình thì có rất nhiều thứ để chia sẻ 😅. Nhưng không vì thế mà bỏ bê các bài viết dài, vì dài thì có nhiều thông tin để chia sẻ hơn.

    Vài lời tâm sự thế thôi chứ hơn một tháng nay mình chưa viết bài viết mới nào vì công việc bận quá. Xong lâu dần cứ trì hoãn lại thành lười. À với cả tháng 5 này rất thích hợp để đọc các cuốn sách về cách mạng á. Có hôm đọc đến 2 giờ sáng mới đi ngủ 🥱

    » Xem thêm
  • Mình mới nhìn thấy một trang web khá thú vị nói về các cột mốc đáng nhớ trong lịch sử phát triển Internet toàn cầu: Internet Artifacts

    Chỉ từ 1977 - khi Internet còn nằm trong hộp thí nghiệm thì nhìn xem - giờ đây Internet đã khiến mọi thứ phát triển đến mức nào 🫣

    » Xem thêm

Vấn đề

Có một lời khuyên dành cho bất kỳ ai làm việc với Node.js là "đừng bao giờ chặn vòng lặp sự kiện". Chặn ở đây có nghĩa là khiến Event Loop không thể luân chuyển nhiệm vụ cần giải quyết. Node.js chỉ có một luồng để xử lý mã JavaScript, nếu một công việc chiếm nhiều thời gian xử lý thì nó sẽ gây ra một cuộc tắc nghẽn nghiêm trọng trong luồng chính. Hãy hình dung nơi tất cả yêu cầu đến sau phải đợi yêu cầu trước hoàn thành thì mới bắt đầu xử lý. Quả là khủng khiếp.

Biết điều đó, tất nhiên Node.js phải cung cấp một số cách giải quyết. Thay vì gọi những hàm đồng bộ thì hãy chuyển sang gọi hàm bất đồng bộ, ví dụ như cùng là đọc một tệp tin nhưng readFile được khuyên dùng hơn readFileSync bởi vì readFile là hàm bất đồng bộ, xử lý trong luồng chính. Ngược lại readFileSync là bất đồng bộ và được thực hiện bên ngoài luồng chính. Ngoài ra, nếu công việc đòi hỏi khả năng tính toán của CPU thì đây là lúc cần biết đến module child_process được tích hợp sẵn trong Node.

child_process là module xuất hiện trong Node.js từ những phiên bản đầu tiên. Sau đó, Node.js bổ sung thêm các module worker_threads có chức năng tương tự như child_process mà API dễ sử dụng hơn. Tôi đã có một bài về Worker threads là gì? Bạn đã biết khi nào thì sử dụng Worker threads trong node.js chưa? bạn đọc có thể tham khảo. Nhưng trong phạm vi bài viết này, hãy tạm quên đi worker_threads và tập trung vào xem child_process là gì và nó được sử dụng như thế nào nhé.

Child process là gì?

Child process là một module của Node.js cho phép tạo ra các tiến trình con (process) độc lập để thực hiện các tác vụ cụ thể. Nó cho phép Node.js chạy nhiều tác vụ đồng thời và tận dụng tối đa sức mạnh của máy chủ. Khi tạo ra một child process, nó sẽ chạy độc lập với parent process (tiến trình cha) và có thể giao tiếp với cha qua luồng (stream), các sự kiện (event)... Các child process được tạo ra có tài nguyên độc lập, giúp giảm thiểu tác động đến các tiến trình khác khi xử lý tác vụ nặng hoặc chẳng may bị lỗi.

Cho dễ hình dung, một ứng dụng Node.js khi khởi động thì nó là một process với một bộ V8 Engine được tạo ra. Để ngăn vòng lặp sự kiện bị chặn, cách tốt nhất là tạo ra một tiến trình khác để xử lý. Khi đó, nó có thể chạy độc lập với tiến trình cha, xử lý rồi trả lại kết quả cho tiến trình cha thông qua một kênh giao tiếp như đã kể đến ở bên trên.

Tùy thuộc vào cách child process được tạo ra mà nó có cách thực hiện nhiệm vụ khác nhau. Có hai cách phổ biến để tạo ra child process là spawnfork. Trong khi fork cố gắng tạo ra một "bản sao" của process cha, có nghĩa là "clone" ra một V8 Engine để xử lý tác vụ thì spawn lại chỉ đơn giản là tạo ra một process thực hiện câu lệnh nào đó. Chi tiết hơn, hãy đi qua từng phương thức xem chúng thực chất là như thế nào.

spawn

spawn là một phương thức để tạo ra một child process mới. Khi sử dụng spawn, ta có thể truyền cho child process các tham số, tùy chọn và đối số cần thiết để thực thi lệnh hoặc file thực thi.

child_process.spawn(command[, args][, options])

Khi child process được tạo ra bằng spawn, nó có thể hoạt động độc lập với process cha, và có thể trao đổi dữ liệu với process cha thông qua pipe hoặc stream. Ta cũng có thể quản lý child process bằng cách theo dõi các sự kiện để biết khi nó hoàn thành hoặc gặp lỗi.

Ví dụ về cách sử dụng spawn:

const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

Ở dòng số 2 chúng ta đang tạo ra một child process và nó thực hiện câu lệnh ls với các tùy chọn '-lh' và '/usr'. Hay nói cách khác, đó là một lệnh:

$ ls -lh /usr

Sau đó, sử dụng on để lắng nghe sự kiện từ child pocess để nhận dữ liệu ở process cha. Trong ví dụ trên, on đang "lắng nghe" trên 3 sự kiện của child process là thành công, thất bại và đóng tiến trình con.

Nếu để ý, có thể thấy trong spawn có thể chạy một lệnh node:

spawn('node', ['index.js']);

Bạn có thể chạy một file .js bằng cách trên trong tiến trình mới, hoặc nhanh hơn là sử dụng fork để đơn giản hóa khả năng sử dụng như trong phần dưới đây.

fork

fork cũng là một phương thức để tạo ra một child process mới, nó là một trường hợp đặc biệt của spawn, hay nói cách khác fork chỉ là một hàm dựa trên spawn. Tiến trình con này chạy một phiên bản độc lập của mã JavaScript được chỉ định. Mã này có thể được đặt trong một tệp hoặc một hàm được truyền dưới dạng tham số cho fork.

child_process.fork(modulePath[, args][, options])

Hàm fork sẽ tạo ra một child process mới, được "sao chép" từ cha (bao gồm những thứ như tạo ra hẳn một bộ V8 engine mới - điều này làm cho fork trở nên tốn kém về mặt tài nguyên), nhưng với một môi trường độc lập và một ID process khác biệt. Tiến trình con này có thể thực hiện các nhiệm vụ độc lập với tiến trình cha, có thể giao tiếp với cha thông qua một kênh IPC (Inter-Process Communication) được cung cấp bởi Node.js.

fork là giải pháp hoàn hảo để chia sẻ tải công việc, xử lý các tác vụ nặng, chạy các đoạn mã không đồng bộ mà không ảnh hưởng đến hiệu suất của cha.

Ví dụ, bạn có một tệp fibonacci.js dùng để tính toán dãy số Fibonacci:

function fibonacci(n) {
  if (n < 2) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

process.on('message', (msg) => {
  const result = fibonacci(msg);
  process.send(result);
});

Sau đó, tạo ra một child_process để xử lý việc gọi hàm fibonacci() trong một tiến trình riêng biệt.

const { fork } = require('child_process');

const child = fork('fibonacci.js');

child.on('message', (result) => {
  console.log(`Fibonacci: ${result}`);
});

child.send(10);
});

Khi nào nên sử dụng child_process cũng như fork hoặc spawn?

Trước tiên, phải nói rằng lựa chọn sử dụng child_process còn phụ thuộc vào bài toán đang cần giải quyết. Vì chi phí để tạo ra một tiến trình con là khá tốn kém cho nên không phải cứ tạo ra càng nhiều thì ứng dụng của bạn sẽ xử lý càng nhanh. Ngược lại, nó có thể nhanh chóng làm cạn kiệt tài nguyên máy chủ cũng như chi phí liên lạc giữa các tiến trình với nhau.

Node.js xử lý I/O không đồng bộ rất tốt, nếu ứng dụng thiên về I/O có thể cần phải quan tâm đến cấu hình sao cho Worker Pools trong libuv được tối ưu nhất chứ không phải là tạo ra nhiều child_process để xử lý I/O không đồng bộ. Bạn đọc có thể tham khảo thêm bài viết Phân biệt tác vụ I/O và tác vụ chuyên sâu CPU để biết cách phân biệt tác vụ I/O với Tác vụ chuyên sâu CPU.

Trong trường hợp ứng dụng cần sự tính toán của CPU nhiều hơn thì child_process lại phù hợp. Lúc này cần phải vận dụng kinh nghiệm sử dụng hai cách tạo ra tiến trình con đã nêu ra ở trên để tối ưu chi phí tài nguyên.

Ví dụ máy chủ cài sẵn phần mềm, lệnh, bash script... và muốn gọi chúng từ Node.js thì hãy sử dụng spawn. Nó chỉ đơn giản tạo ra một tiến trình để thực hiện câu lệnh trong spawn rồi trả về kết quả, vừa nhanh vừa tiết kiệm.

fork thì lại phù hợp khi công việc nằm trong một tệp hoặc một hàm JavaScript. fork tạo ra một bản sao V8 Engine và có toàn quyền truy cập vào những module (node_modules) có trong ứng dụng của bạn. Hơn nữa, vì là tiến trình độc lập nên chẳng may tiến trình bị lỗi thì không gây ảnh hưởng đến luồng chính.

Tổng kết

child_process là một module trong Node.js cho phép tạo ra các quy trình con độc lập để thực hiện các tác vụ cụ thể, nhằm ngăn việc chặn vòng lặp sự kiện. Có nhiều cách để tạo ra một child process thông qua module child_process, trong số đó là hai phương thức spawnfork. spawn thì được dùng để chạy một lệnh cụ thể trong khi fork tạo ra một bản sao của V8 Engine để chạy một đoạn mã JavaScript. Tùy vào bài toán mà việc lựa chọn sử dụng child process sao cho hợp lý, tránh lãng phí tài nguyên cũng như tăng hiệu suất cho ứng dụng.

Tài liệu tham khảo:

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (1)

Nội dung bình luận...
Avatar
Jess Vanes1 năm trước
quưkdnqmxncks skdnc akaofmxnak
Trả lời