Sau khi dạo chơi hết C, C++, Java giờ page .NET xin giới thiệu một bài viết mới về JavaScript
Mô hình xử lý [đồng thời] trong JS có phần khác so với các ngôn ngữ truyền thống như C hay Java, trong đó mỗi chương trình JS (một trang web, web worker hay cross origin iframe) sẽ chỉ chạy trên một thread.
Vì chỉ có một thread nên trong bài viết này ta quy ước T là tên của thread đó.Tương tự như những gì ta đã học về C, thread T cũng sẽ có 2 phần bộ nhớ là:
– Stack: chứa các tham số, biến local và địa chỉ trở về.
– Heap: chứa các đối tượng được cấp phát động. Nhớ là ngay cả khi bạn khai báo 1 biến trong một function, nếu đó không phải là một biến kiểu primitive types passed by value (bool, integer…) thì nó vẫn được cấp phát trên Heap, và chỉ có biến tham chiếu là nằm trong Stack.
vd:
function foo() {
let a = 10; // a nằm trong stacklet
b = { text: "Biến b vẫn trong stack, nhưng đối tượng chứa chuỗi này nằm trên heap, b chỉ là một tham chiếu đến đối tượng đó mà thôi" };
}
Ngoài ra, T sẽ có thêm một thành phần nữa là một hàng đợi thông điệp (message queue), mỗi một thông điệp chứa một yêu cầu (lệnh) nào đó, khi chạy T sẽ kiểm tra hàng đợi này, nếu có message nào trong đó, nó sẽ lấy ra và thực thi. Các hàng đợi sự kiện (message queue) này không mới, nếu bạn đã từng lập trình cho Windows, sử dụng trực tiếp Windows API và làm việc với các cửa sổ, bạn sẽ thấy message queue này rất quen thuộc (https://docs.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues).
Mô hình xử lý dựa trên message queue này trong JavaScript được gọi là event loop.
Ai đưa lệnh vào message queue?
Khi một app JS chạy, sẽ có rất nhiều sự kiện xảy ra liên tục, ví dụ trên một trang web, sẽ có các sự kiện như nhấn chuột, di chuyển chuột, cuộn cửa sổ… Hoặc các sự kiện hoàn thành việc đọc file khi bạn gọi fs.readFile, gọi các hàm AJAX… nếu ta có sử dụng các hàm callback, các lời gọi hàm đó sẽ được đặt vào trong message queue. T sẽ đọc và xử lý lần lượt theo thứ tự FIFO.
Khi bạn gọi setTimeout, hàm callback của nó cũng sẽ được đặt vào trong message queue.
Một điểm lưu ý là khác với mô hình multithread, các function trong JS sẽ luôn chạy từ đầu đến cuối và không bao giờ bị ngắt nửa chừng bởi một thread khác. Vì vậy làm việc với các tài nguyên dùng chung trong JS cũng dễ thở hơn rất nhiều so với Java hay C. Tuy nhiên nhược điểm là nếu chúng chạy quá lâu, các message trong queue sẽ chậm được xử lý. Đây là một điều rất quan trọng khi tối ưu các trang web, bởi nếu có một function nào đó tiêu tốn quá nhiều thời gian, các phần còn lại sẽ phải nằm chờ cho đến khi nó hoàn thành.
Cách giải quyết là chỉ có các tài nguyên cần thiết nhất được đưa lên đầu, những gì không cần ngay có thể xử lý sau nhờ setTimeout, hay chờ người dùng cuộn xuống dưới.Một trong ưu điểm của mô hình event loop này là các function có sẵn JS sẽ không bao giờ bị block (*)(**) – tức T phải dừng lại chờ nó thực thi xong mới làm tiếp công việc khác. Nếu để ý, bạn sẽ thấy trong JS các hàm tiêu tốn thời gian sẽ luôn nằm ở dạng callback, thay vì: “Làm cái đó đi, tôi sẽ chờ cho đến khi xong mới làm tiếp cái khác” giờ sẽ trở thành “Làm cái đó đi, khi nào xong cứ bỏ kết quả vào hàng đợi, tôi đi xử lý cái khác”. Nhờ đó ứng dụng JS có khả năng xử lý được nhiều công việc khác nhau dù chỉ có một thread. Tất nhiên đằng sau đó, JavaScript engine cũng phải có khả năng hỗ trợ cơ chế này, bằng non-blocking I/O, hoặc (có thể)(**) thậm chí bằng việc tạo ra các thread chạy nền khác.Kết luận:JavaScript không có cơ chế multithread, những công việc cần làm sẽ được đưa vào hàng đợi và được xử lý tiên tục bởi thread chính của chương trình.
Ghi chú:
(*) Một số function cũ trước đây vẫn có thể block, bao gồm alert và synchronous XHR, tuy nhiên nên hạn chế sử dụng chúng.
(**) Việc implement cơ chế event loop có thể khác nhau, ví dụ trong nodejs, có nhiều message queue cho từng loại sự kiện, tuy nhiên chúng vẫn được xử lý lần lượt bởi 1 thread, nên từ góc độ người dùng ta vẫn có thể coi là chỉ có một message queue.