Vấn đề về event loop
-
Mình có 1 hình vẽ minh họa cho event-loop trong NodeJS:
Như trong hình vẽ thì các event được thực thi tuần tự, nghĩa là event này chạy xong thì mới tới event kia.
Mình cũng đã kiểm chứng điều này là đúng bằng cách sau: (code: http://paste2.org/bNU3ZH9d
- Nếu mình vào route "/" thì trình duyệt hiển thị chữ "ok" rất nhanh.
- Nếu mình vào route "/hello" thì trình duyệt loading rất lâu (hết 15s vì mình cho chờ 15s); lúc này thì route "/" cũng không vào được.
=> các event được thực thi tuần tự nhau.
Vậy thì theo mình đánh giá thì event loop không có gì hay ho cả, chẳng qua là nó xử lý từng sự kiện 1 nên có thể chịu được lượng request lớn. Nhưng về hiệu năng thì sẽ giảm rất nhiều nếu 1 event nào đó cần thời gian thực thi lâu.
Ví dụ khi như mình viết 1 trang web cập nhật tỷ giá usd/vnd, euro/vnd... (giả sử tổng thời gian cập nhật tất cả các loại tỷ giá là 30s). Bây giờ có 2 người truy cập vào web của mình, thì người thứ hai phải chờ người thứ nhất có kết quả xong thì mới tới lượt họ.
=> Tổng thời gian để 2 người này có kết quả tỷ giá là ít nhất 60s.
Trong khi đó, nếu chạy đa luồng thì mình có thể cho công việc cập nhật tỷ giá trong luồng con, và các luồng con chạy song song nhau => chỉ mất 30s để cả 2 người xem có được kết quả tỷ giá.P/s: mình mới tìm hiểu về NodeJS và đang thắc mắc về vấn đề event-loop này; không biết mình hiểu như vậy có đúng không?
Cám ơn mọi người đã đọc
-
Vấn đề sẽ được giải quyết bằng cách đưa vòng loop xuống một
child process
và kết hợp với một native binary nào đó, hoặc đơn giản là đưa loop vào cácwebworker-threads
. Nhưng nhiều lần test bằngwebworker-threads
tôi thấy nó bị delay khi khởi tạo. Như ví dụ của bạn đợi 15s, nếu có 2 request tới '/hello' cùng lúc đến thì có thể:- request đầu có thể đúng 15s trả về kết quả cho user
- nhưng request thứ 2 phải là 15.5 - 17s
=> tổng cộng 17s để xử lý 2 request
Dĩ nhiên là với request tới '/' thì nó trả lại ngay lập tức.
Do đó trong khi chờ một module viết bằng javascript để xử lý tốt vấn đề này, thì tôi vẫn thiên vềchild process
và gọi một chương trình native hơn. Các native này có thể viết bằng C++, GoLang để tận dụng hiệu năng. -
child process là mình thực thi 1 process mới hả bạn? Nếu là thực thi 1 process mới thì có vẻ không tối ưu lắm.
Ví dụ 1000 người truy cập cùng lúc thì nó execute 1000 cái process thì không khả thi tý nào. Khi mình quan sát mở 1 process thì thấy nó tốn CPU và RAM khá nhiều.
-
@thanhps42
Nhắc lại cho bạn là một App nodejs cơ bản là single thread bạn nhé. Vì sao Nodejs hay được ứng dụng có thể phục vụ rất nhiều request một lúc là vì nó đơn luồng - nó không phải tạo thread mới để trả lại TCP request như PHP hay các loại blocking I/O khác.
Tuy nhiên vì đơn luồng nên các tác vụ dùng CPU hoạt động chuyên sâu , đặc biệt là các vòng loop sẽ blocking I/O.
Để giải quyết một phần điều này, bộ V8 engine của Google có những cách tối ưu để các vòng lặp, kết thúc một quá trình nó sẽ gọi một callback để thực thi mã của lập trình viên, nó cũng giống như một thread nhưng thay vì tạo ra một process mới nó nằm trong process ứng dụng lập trình viên. MÔT PHẦN thôi nhé, còn lại thì không.Quay lại giả thiết 1000 người truy cập per times của bạn, liệu rằng ;
- toàn bộ 1000 người đó có gọi các quá trình dùng đến nhiều CPU chuyên sâu ko ? hay một số ít, số còn lại chỉ nhận kết quả ?
- Bạn không định dùng load balancing và scalability
Nếu có tận 1000 người/times truy vấn dùng phương thức nhiều tài nguyên và không áp dụng các biện pháp cân bằng và mở rộng mà lại chạy trên 1 process thì điều đó bất khả thi !
Dùng
child process
là giải pháp đã được nhiều lập trình viên Nodejs trên TG khuyên dùng khi động đến các tác vụ phải dùng CPU chuyên sâu như render photos, video mà lại có lượng truy cập lớn (dĩ nhiên có dùng cả load balancing và scalability ). Cách này là dễ áp dụng nhất để giải quyết vấn đề này.Còn một cách nữa, hiếm khi tôi thấy họ dùng, chỉ thấy mấy Thánh hiểu sâu về V8, C, C++ và bộ mã nguồn của Nodejs . Họ viết lại NODEJS hoặc cùi hơn là viết ADDON cho nó.
Thôi viết đến đây thôi, dài quá, 2/9 được nghỉ mà =))
-
Rikky here:
@thanhps42 cái loop của bạn không phải event loop nhé. Muốn thử nghiệm thực sự, vui lòng thay while thành setInterval.@hidemanvn Nghe ông giải thích mà tôi thấy bối rối VL! Cảm giác như là tôi chả biết cái gì về Node.JS luôn đấy!
Nhất ông đấy nhé! Tôi phải login bằng đt chỉ để rep bài này thôi đấy
-
@tung hình như tk admin vẫn bị relate với github acc của minh, xoá giúp mình nhé
-
@tung đã nói trong Vấn đề về event loop:
Rikky here:
@thanhps42 cái loop của bạn không phải event loop nhé. Muốn thử nghiệm thực sự, vui lòng thay while thành setInterval.@hidemanvn Nghe ông giải thích mà tôi thấy bối rối VL! Cảm giác như là tôi chả biết cái gì về Node.JS luôn đấy!
Nhất ông đấy nhé! Tôi phải login bằng đt chỉ để rep bài này thôi đấy
Bạn nói hay vl
-
@tung có vẻ bạn thanhps42 bị nhầm lẫn giữa "event loop" với vòng lặp (loop/iteration).
-
@ndaidong đã nói trong Vấn đề về event loop:
@tung có vẻ bạn thanhps42 bị nhầm lẫn giữa "event loop" với vòng lặp (loop/iteration).
Không bạn ơi, cái vòng lặp là mình làm để ví dụ cho 1 công việc nào đó tốn thời gian. Ý mình là người ta vào 1 route, mà route này phất 15s mới xử lý xong rồi mới trả về kết quả cho người dùng, thì lúc này nodejs đang bận xử lý cái công việc đang dang dở kia nên nó không thể tiếp nhận thêm request của người dùng mới (event mới).
P/s: có bạn hidemanvn hiểu đúng ý mình đó.
-
@thanhps42 Trong thời gian xử lý 1 tác vụ I/O thì node vẫn nhận thêm các request khác, còn việc xử lý tác vụ I/O đó như thế nào thì sẽ được để trong 1 hàm callback, tác vụ xong, hàm callback sẽ được gọi và thực thi câu lệnh bên trong nó.
Còn muốn hiểu rõ về event loop, bạn nên xem video này, được recommend khá nhiều trong những tuts về node:
https://www.youtube.com/watch?v=8aGhZQkoFbQhappy coding =))
-
@thanhps42 ok. Nếu vậy thì nó là về sync vs async.
Ví dụ khi kịch bản đang đọc file bằng readFileSync, nó sẽ không thể xử lý các request khác như trường hợp vòng lặp while của bạn.
let s = fs.readFileSync('./data.json'); console.log(s);
Nếu đọc file bằng readFile thì callback sẽ được đẩy vào message queue và lúc này mới là câu chuyện của Event loop. Các request đến vẫn được xử lý bình thường trong khi fs đang đọc nội dung từ drive.
fs.readFile('./data.json', 'utf8', (err, s) => { console.log(s); });
Đó là lý do vì sao trong Node.js người ta khuyến khích viết async thay vì sync.
Trở lại với ví dụ của bạn, tuy các vòng lặp chiếm resource để xử lý nhưng không được cho vào message queue nên hòan toàn không liên quan đến event loop. Khi có các tác vụ chiếm dụng tài nguyên kiểu đó (big data, image processing...) thì cách phổ biến vẫn là ném sang cho 1 thread con xử lý.
-
Mới tìm hiểu khẳng định vl
-
M nghĩ cũng chỉ là chưa biết thôi, cách diễn đạt có thể hơi tự tin quá về kt của m, nhưng nhận được 2 rep có "vl" từ mode và ad chắc sẽ ko quay lại dd lần nữa mất
-
-
Hoang mang vl, thế ai đúng ai sai?
các bác chém vừa thôi, biết thì hãy nói không lại hỏng cả 1 thế hệ -
@Vũ mình có ghi rõ là theo quan điểm của mình mà, và ở dưới mình cũng nói đây là thắc mắc của mình nhờ mọi người giải đáp chứ mình đâu có khẳng định ở chỗ nào đâu.
Chân thành cám ơn đến @hidemanvn @ndaidong và @thelonglqd đã giúp mình hiểu được vấn đề.
Cám ơn cả nhà nhiều ợ
-
Sự chậm trễ trong thực thi kết nối thứ 2 trong ví dụ mà bạn đưa ra xuất phát từ việc tác vụ CPU chuyên sâu ở kết nối thứ nhất và tác vụ xử lý kết nối thứ 2 đều được thực thi bởi một luồng (thread) duy nhất. Điều này là hoàn toàn bình thường và hợp lý.
Mình xin lấy một ví dụ để bạn dễ hiểu hơn.
Giả sử bạn đang làm việc cho một công ty với tư cách là một developer. Vào một hôm đẹp trời, thay vì thuê thêm nhân viên, sếp của bạn quyết định giao toàn bộ công việc của công ty đó cho bạn. Từ việc quét nhà, dọn dẹp, đến việc viết báo cáo, lập trình hay đi hội thảo. Tất cả các việc do một mình bạn đảm đương. Bạn có cho rằng bạn sẽ hoàn thành toàn bộ công việc đó nhanh như khi bạn chỉ phải làm duy nhất một công việc là lập trình? Câu trả lời chắc chắn sẽ là không. Để đảm bảo về mặt thời gian, việc bạn cần làm là yêu cầu sếp thuê thêm người làm và bố trí lại công việc cho phù hợp.Quay lại với ví dụ mà bạn đưa ra, việc tất cả các tác vụ được thực thi bởi một luồng duy nhất rất giống với ví dụ minh họa trên. Trên thực tế, những tác vụ I/O (I/O tasks) hoặc tác vụ CPU chuyên sâu (CPU intensive tasks) thường hoặc nên được thực thi ở một luồng khác. Việc này giúp chúng ta tránh được thời gian phải chờ đợi trong quá trình các tác vụ đó được thực thi. Và khi các tác vụ đó được thực thi trên một luồng khác, chúng ta cũng cần biết khi nào thì các chúng được bắt đầu, được thực thi, hay đã thực thi xong thông qua việc sử dụng
Event
để gửi tín hiệu thông báo ngược trở lại luồng chính về trạng thái của quá trình thực thi. Quá trình này cứ được lặp đi lặp lại như vậy nên thường được gọi là mô hìnhEvent Loop
. Javascript không phải là ngôn ngữ duy nhất áp dụng mô hình này.Bên cạnh đó, việc nói Nodejs đơn luồng là không thực sự chính xác. Thực chất, chỉ có đoạn code mà bạn viết ra ở tầng ứng dụng (application layer) là được thực thi đơn luồng. Phía sâu bên trong Nodejs, các tác vụ I/O hay CPU chuyên sâu đa số được thực thi đa luồng, và được viết bằng các ngôn ngữ khác không phải Javascript (C, C++,...).
Để kết luận, mình muốn khẳng định rằng
Event Loop
chỉ là một trong những mô hình được thiết kế cho việc quản lý các tác vụ không đồng bộ đòi hỏi nhiều thời gian thực thi thông qua công cụEvent
. Nó không phải là một phép màu mang lại sự cải thiện về tốc độ. Cái chính vẫn nằm ở khả năng lập trình, khả năng quản lí và xử lý tác vụ một cách hợp lý của các developer. -
@thanhps42 cmt cho xôm thôi. Ko có ý gì nha :))
-
@tresdin cám ơn câu trả lời của bạn nhiều. Câu trả lời chất lượng lắm bạn
-
@tresdin đã nói trong Vấn đề về event loop:
Thực chất, chỉ có đoạn code mà bạn viết ra ở tầng ứng dụng (application layer) là được thực thi đơn luồng. Phía sâu bên trong Nodejs, các tác vụ I/O hay CPU chuyên sâu đa số được thực thi đa luồng, và được viết bằng cá
Great