Event Loop trong Node.js hiếm khi được nhắc đến. Node event Loop là gì ? Nó cung cấp gì cho lập trình viên ? Và nó giúp chúng ta phát triển như thế nào ?

Kết luận cơ bản thứ nhất là hoạt động I/O rất tốn kém tài nguyên:

io-cost

Sự lãng phí lớn nhất với các công nghệ lập trình hiện tại xuất phát từ việc chờ đợi hoạt động I/O hoàn tất. Có một số cách mà người ta có thể giải quyết với những tác động hiệu quả (theo Sam Rushing).

  • Đồng bộ (Synchronous): Xử lý 1 request tại 1 thời điểm, và lần lượt. Pros: Đơn giản; Cons: Bất kỳ 1 request có thể chứa tất cả các requests khác.
  • Rẽ nhánh 1 tiến trình mới (Fork a new process): Khởi tạo 1 tiến trình mới để xử lý mỗi request. Pros: Đơn giản; Cons: khó mở rộng khả năng, hàng trăm kết nối tương đương với hàng trăm tiến trình.
  • Đa luồng (Threads): Khởi tạo 1 luồng mới để xử lý mỗi request. Pros: đơn giản, thuận lợi cho hoạt động của Kernel hơn là Fork, do đó Threads ít chi phí hơn; Cons: Máy tính có thể không hỗ trợ đa luồng, và lập trình đa luồng có thể trở lên rất phức tạp, với việc điều khiển truy cập các tài nguyên chia sẻ.

Kết luận cơ bản thứ 2 là luồng-trên-kết-nối là rất tốn bộ nhớ: [ví dụ: Apache tiêu tốn bộ nhớ khủng khiếp hơn so với Nginx]

Apache là đa luồng: Nó sinh ra 1 thread trên 1 request đến (hoặc sinh ra hẳn 1 process, phụ thuộc vào cấu hình). Bạn có thể thấy bộ nhớ sử dụng tăng lên đáng kể khi nhiều clients kết nối đồng thời đến 1 webserver. Nginx và Node.js là không hoạt động đa luồng, bởi vì threads và processes tiêu tốn lượng lớn bộ nhớ RAM. Chúng là đơn luồng single-threaded, nhưng dựa trên sự kiện. Điều này loại bỏ được chi phí tạo ra bởi hàng ngàn các threads/processes bằng việc sử lý nhiều kết nối trong 1 luồng đơn.

Node.js chỉ giữ 1 luồng đơn trên đoạn mã của bạn …

Nó thực sự chạy 1 luồng đơn: Bạn không thể làm bất kỳ đoạn mã song song; ví dụ làm 1 việc là “sleep” block server và không làm gì trong 1 giây:

while(new Date().getTime() < now + 1000) {
   // do nothing
}

Vì vậy, khi đoạn mã chạy, Node.js sẽ không respond tới bất kỳ 1 request khác đến từ các clients, vì nó chỉ có 1 luồng để thực thi đoạn mã của bạn. Cho dù bạn có gia tăng thêm CPU nó vẫn tiếp tục block các requests đến khác.

…nhưng, mọi thứ vẫn chạy song song trừ đoạn mã bạn lập trình

Không có cách nào làm cho mã chạy song song trong 1 request đơn. Nhưng, tất cả hoạt động I/O là có sự kiện và không đồng bộ, vì thế đoạn mã sau đây sẽ không block server.

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Nếu bạn thực hiện mã trên trong 1 request, các request khác có thể được tiếp tục xử lý trong khi database đang chạy để đọc dữ liệu.

Tại sao điều này tốt ? Khi nào chúng ta chuyển từ đồng bộ sang không đồng bộ/song song

Việc thực thi đồng bộ là tốt, vì nó đơn giản để viết mã (so với đa luồng, trong đó tương tranh đồng thời có xu hướng dẫn đền WTFs)

Trong node.js, bạn không phải lo về những gì xảy ra ở backend: chỉ cần sử dụng callbacks khi bạn đang thực thi hoạt động I/O; và bạn cần đảm bảo được rằng mã của bạn không bao giờ bị gián đoạn và hoạt động I/O sẽ không block các requests khác mà không phải chịu chi phí cho Thread/Process trên 1 request đến (e.g. chi phí bộ nhớ trong Apache).

Có thao tác không đồng bộ I/O là tốt, bởi vì I/O là tốn kém hơn nhiều so với việc thực thi mã và chúng ta nên làm 1 cái gì đó khác hơn là chờ I/O hoàn tất.

Single Process IO

Một sự kiện lặp Event Loop là “một cấu trúc xử lý các sự kiện bên ngoài và chuyển đổi chúng thành những lời gọi callback”. Vì thế I/O gọi là điểm mà Node.js có thể chuyển từ 1 request sang 1 request khác. Tại 1 lời gọi I/O, mã của bạn lưu điểm gọi lại callback và trả lại điều khiển cho Node.js. Điểm callback sẽ được gọi lại sau khi dữ liệu thực sẵn sàng.

Tất nhiên, trong backend, có các threads và processes truy cập DB. Những luồng thực thi này không được đưa ra 1 cách rõ ràng, vì thế bạn không cần lo lắng về chúng (được kiểm soát bởi các I/O). Ví dụ với tiến trình database, hoặc các tiến trình khác sẽ là không đồng bộ từ các request riêng biệt do đó kết quả từ những luồng này được trả về thông qua Event loop tới mã của bạn. So sánh với mô hình Apache, có ít hơn các threads được sinh ra, vì đa luồng là không cần thiết cho mỗi kết nối đến; chỉ khi bạn thực sự phải làm 1 việc nào đó song song trong Node.js

Trên đây là đã có thể hiểu cơ bản và có cái nhìn về khái niệm Event Loop và hoạt động I/O trong Node.js Để tìm hiểu thêm về Event Loop các bạn tham khảo thêm các bài viết ở link sau:

Tham khảo

http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
http://strongloop.com/strongblog/node-js-event-loop/
http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

About The Author