Kiến trúc Node.js - Event Loop

Kiến trúc Node.js - Event Loop

Tin ngắn hàng ngày dành cho bạn
  • Hôm nay mình đã cố gắng đi hẳn 8k bước trong một phiên để đo lường cho các bạn thấy. Quả là không ngoài dự đoán khi thời gian đi lên đến hơn 1 giờ và quãng đường ~6km 🤓

    À vài hôm nữa là hết tháng, tức là cũng tròn 1 tháng mình bắt đầu thói quen đi bộ mỗi ngày với mục tiêu 8k bước. Để đầu tháng sau mình tổng kết lại xem thế nào luôn ha.

    » Xem thêm
  • Lâu lắm mới đọc được bài viết tâm huyết như thế này The many, many, many JavaScript runtimes of the last decade. Trong bài viết tác giả kể lại hành trình về sự phát triển của môi trường chạy mã JavaScript (JS Runtime), rằng đến ngày hôm nay thì chúng ta đã và đang có rất nhiều nơi có sự góp mặt của JavaScript. Ngoài ra tác giả cũng nhắc đến rất nhiều kiến thức bên lề, đọc một tí thôi mà khám phá thêm được bao nhiêu cái mới 🤓

    » Xem thêm
  • Thêm một ứng dụng rất thực tế của mấy đứa Agent tổng hợp thông tin.

    Hôm qua hôm kia mình có mấy đứa em mới thi tốt nghiệp THPT và đã có điểm. Giờ là giai đoạn tụi nó chọn trường và dĩ nhiên là cần phải tham khảo điểm chuẩn của các trường, các ngành trong quá khứ, ít nhất là soi trong 2024. Cơ mà điểm thì không phải năm nào cũng như nhau, vậy điều gì tham gia vào quá trình quyết định điểm chuẩn của ngành học năm nay?

    Có nhiều yếu tố, trong đó yếu tố quan trọng là phổ điểm của năm. Ví dụ phổ điểm năm ngoái ở mức trung bình 23 điểm cho tổ hợp 3 môn khối A00. Năm nay tụt xuống mức 22.5 thì nhiều khả năng điểm chuẩn sẽ có xu hướng giảm theo. Cơ mà lưu ý rằng tất cả chỉ là phỏng đoán chứ không thể chắc chắn.

    Vì vậy mình dùng ngay ChatGPT và Manus để nhờ nó tổng hợp, phân tích và so sánh phổ điểm của 2024 và 2025 để lấy nguồn tham khảo trước khi quyết định tư vấn cho các em 😄

    » Xem thêm

Vấn đề

Trong bài viết trước chúng ta đã biết Event loop là một thành phần quan trọng trong JavaScript/Node.js. Nhiệm vụ của nó là mang các hàm callback ở trong Callback queue quay trở lại call stack. Nhưng câu chuyện chưa dừng lại ở đó, Event loop có cách mang các hàm quay trở lại khác nhau. Hay nói cách khác là các hàm callback có thứ tự ưu tiên khác nhau. Độ ưu tiên càng lớn thì Event loop càng mang nó quay trở lại call stack nhanh hơn. Hiểu được cơ chế này bạn đọc sẽ viết ra được chương trình tối ưu về mặt hiệu suất.

Trước tiên hãy tìm hiểu các pha (phases) của Event loop để biết thứ tự ưu tiên của các hàm callback.

Các pha (Phases) của Event loop

Hãy tưởng tượng Event loop như một bánh xe chia thành nhiều ngăn, cụ thể ở đây là 6. Để quay hết một vòng cần phải đi qua đủ 6 ngăn. Ở mỗi ngăn, Event loop kiểm tra xem có hàm callback nào đáp ứng tiêu chí thì sẽ "bốc" nó quay trở lại call stack.

Dưới đây là sơ đồ các pha của vòng lặp sự kiện.

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Bây giờ hãy đi vào chi tiết từng pha.

timers

Timers là nơi xử lý các callback của setTimeoutsetInterval. Khi thời gian chờ được thiết lập đã trôi qua, callback tương ứng sẽ được thực thi tại đây.

Cũng cần phải lưu ý rằng đây là thời gian chờ tối thiểu, không phải chính xác tuyệt đối do phụ thuộc vào độ trễ của các pha khác.

Ví dụ.

const fs = require('node:fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();
setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;
  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();
  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

Giả sử someAsyncOperation mất 95ms để hoàn thành, nó nhỏ hơn thời gian chờ 100ms của hàm setTimeout. Về lý thuyết, lệnh console.log trong setTimeout in ra delay bằng 100ms đúng với thời gian chờ nhưng hãy để ý hàm callback của someAsyncOperation đang "kẹt" ở pha pool (lệnh while đã kịp đưa vào call stack) nên mất một độ trễ nhất định thì callback trong setTimeout mới được đưa vào call stack. Lúc này chúng ta thấy thời gian delay lớn hơn 100ms vài đơn vị.

pending callbacks

Pending callbacks xử lý các callback cho một số hoạt động hệ thống như các loại lỗi TCP, lỗi trong hệ thống tệp I/O (như fs).

idle, prepare

Idle, prepare là giai đoạn sử dụng nội bộ của Node.js nhiều hơn là mã của người dùng.

poll

Poll là giai đoạn trung tâm của xử lý bất đồng bộ. Poll chờ và xử lý hầu hết các sự kiện I/O như đọc/ghi tệp tin, kết nối mạng,... Poll có hai chức năng chính: Tính toán thời gian cần chặn và Xử lý các sự kiện trong hàng đợi poll.

Khi vòng lặp sự kiện vào đến giai đoạn poll, nó thực hiện 2 công việc:

  • Nếu có callback I/O: thực thi ngay.
  • Nếu không có callback và:
    • Có callback setImmediate: kết thúc poll, chuyển sang pha check.
    • Không có setImmediate và chưa hết thời gian chờ: chờ tiếp.

Poll có một ngưỡng thời gian chờ nhất định khi nó đang trống trước khi chuyển sang giai đoạn tiếp theo.

check

Check xử lý các callback được đăng ký qua setImmediate.

close callbacks

Close callbacks xử lý các callback khi một tài nguyên kết nối đóng đột ngột. Ví dụ socket.destroy() hoặc sự kiện close được phát ra.

Ngoài 6 pha trên, Node.js còn có một "pha đặc biệt" nữa là process.nextTick().

process.nextTick()

process.nextTick() không được hiển thị trong sơ đồ 6 pha của vòng lặp sự kiện mặc dù nó là một phần của API không đồng bộ. Điều này là do về mặt kỹ thuật nó không phải là một phần của vòng lặp sự kiện. Thay vào đó, hàm callback của process.nextTick() sẽ được xử lý sau khi hoạt động của pha hiện tại hoàn tất. Hay nói cách khác, nó luôn được ưu tiên xử lý đầu tiên mỗi khi vòng lặp sự kiện bước vào pha tiếp theo.

Cũng chính vì process.nextTick() luôn được thực thi trước khi bước vào pha nên nó có khả năng chặn vòng lặp sự kiện như ví dụ dưới đây.

function endlessLoop() {
  process.nextTick(endlessLoop);
}
endlessLoop();

Trong bài viết tiếp theo, chúng ta hãy đi sâu vào tìm hiểu process.nextTick() nhé.

Kết luận

Event Loop là một thành phần cốt lõi trong kiến trúc của Node.js, đóng vai trò điều phối các tác vụ bất đồng bộ bằng cách đưa các hàm callback từ hàng đợi (Callback queue) vào Call Stack để thực thi. Trong bài viết này, chúng ta đã khám phá 6 pha chính của Event Loop, bao gồm timers, pending callbacks, idle, prepare, poll, check, và close callbacks, mỗi pha đảm nhận nhiệm vụ xử lý các loại callback khác nhau. Timers chịu trách nhiệm cho các hàm setTimeoutsetInterval, trong khi Poll là trung tâm xử lý các sự kiện I/O và quyết định chuyển tiếp giữa các pha. Ngoài ra, setImmediate trong pha Check và các callback đột ngột trong Close đều có vai trò riêng biệt trong cơ chế vận hành. Đặc biệt, process.nextTick() tuy không thuộc Event loop nhưng lại có mức độ ưu tiên cao nhất, đảm bảo callback của nó luôn được thực thi ngay lập tức sau pha hiện tại.

Hiểu rõ cơ chế hoạt động của Event Loop và thứ tự ưu tiên của từng pha là chìa khóa để tối ưu hóa hiệu suất chương trình Node.js. Điều này giúp bạn kiểm soát tốt hơn các tác vụ bất đồng bộ, từ đó giảm thiểu độ trễ và tối ưu hóa trải nghiệm tổng thể. Trong bài viết tiếp theo, chúng ta sẽ đi sâu hơn vào process.nextTick() để khai thác triệt để tiềm năng mà Node.js mang lại.

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 (0)

Nội dung bình luận...