12/08/2018, 16:38

Xử lý song song và Event loop model trong Javascript

Đã bao giờ bạn gặp đoạn code sử dụng setTimeout với thời gian bằng 0 chưa? console . log ( 'Me first, absolutely!' ) ; setTimeout ( function ( ) { console . log ( 'No, me last' ) ; } , 0 ) ; console . log ( 'Me last' ) ; Nếu bạn thử chạy đoạn code ...

Đã bao giờ bạn gặp đoạn code sử dụng setTimeout với thời gian bằng 0 chưa?

console.log('Me first, absolutely!');
setTimeout(function() {
    console.log('No, me last');
}, 0);
console.log('Me last');

Nếu bạn thử chạy đoạn code trên, No, me last sẽ hiện ra sau cùng.

// Result:
// Me first, absolutely!
// Me last
// No, me last

Nếu bạn sử dụng javascript thường xuyên, chắc bạn ko ngạc nhiên gì với ví dụ này. Theo cách hiểu thông thường, nếu thời gian bằng 0 tức là ngay lập tức, thì Me last sẽ phải hiện ra sau cùng. Vậy thực sự setTimeout đã làm gì để callback của nó hiện ra sau cùng trong đoạn code trên?

Single Thread: tính chất "xong cái này mới đến cái khác" của Javascript Runtime

Đầu tiên chúng ta sẽ tìm hiểu qua về Javascript Runtime. Trong JR, mỗi lệnh luôn được xử lý hoàn toàn trước khi lệnh khác được xử lý. Một khi hàm chạy, nó không thể bị chặn lại và sẽ chạy xong xuôi trước khi các đoạn code khác có thể chạy. Tính chất này khác biệt so với ngôn ngữ đa luồng như C (khi một hàm được chạy trong một luồng, nó có thể bị code ở một luồng khác chặn lại). Điểm bất lợi của tính chất này là nếu một lệnh mất quá nhiều thời gian để hoàn tất, ứng dụng sẽ không thể xử lý các sự kiện tương tác của người dùng như click hay scroll. Trình duyệt sẽ thông báo "a script is taking too long to run" nếu vấn đề này xảy ra. Chính vì thế các bạn có thể đã đọc ở đâu đó (ví dụ một blog về code) khuyên các bạn cần chia nhỏ code JS để có cách lệnh ngắn, không nên viết một hàm quá dài.

Vậy điều gì tạo nên tính chất Non-blocking?

Khi các bạn sử dụng JS, các bạn có thể thấy nhiều khái niệm rõ ràng đi ngược lại với tính chất "xong cái này mới đến cái khác" điển hình như ajax. Những khái niệm này tạo nên tính chất non-blocking. Vậy điều gì làm nên khái niệm non-blocking trong Javascript? Đó chính là mô hình Event loop. Trong môi trường trình duyệt chúng ta có WebAPIs, ví dụ DOM, ajax, setTimeout ..., trong môi trường Node server, chúng ta có các APIs như thao tác với file, truy vấn DB, ... Trên trình duyệt, các lệnh được thêm vào bất cứ khi nào 1 sự kiện (event) xảy ra và mỗi lệnh sẽ có 1 event listener đính kèm. Ví dụ với DOM api chúng ta có sự kiện on click, với ajax chúng ta có sự kiện on load, setTimeout thì on done, ... Event loop sẽ dựa trên các events từ những api này điều phối các lệnh một cách liên tục.

Mô hình Event loop

Để giải thích được bí ẩn setTimeout 0s, chúng ta sẽ cần nắm được quá trình các lệnh được điều phối bằng mô hình Event loop như thế nào. Chúng ta sẽ sử dụng đoạn code trong hình làm ví dụ:

Như các bạn nhìn trong hình vẽ, chúng ta sẽ có 3 khu vực mà các lệnh sẽ được xử lý gồm:

  • Stack là khu vực chính mà các lệnh được thực thi
  • WebAPIs là khu vực tiền xử lý các lệnh thuộc về WebAPIs trước khi lệnh được đẩy vào stack
  • Task queue là khu vực chờ của các các tập lệnh sau khi được tiền xử lý bởi API trước khi vào stack và được điều phối bởi Event Loop.

Đầu tiên, tập lệnh của chúng ta được gọi, hàm main() sẽ được gọi trong stack.

Tiếp đến, lệnh console.log('Hi') được gọi. Lệnh này ko trực thuộc WebAPI nên được đẩy trực tiếp vào stack.

Tiếp theo, lệnh setTimeout được gọi (phần quan trọng đây này). Đây là lệnh - như đã nói ở trên - trực thuộc WebAPIs, chính vì thế, sau khi được đẩy vào stack và thực thi xong, nó sẽ được chuyển tiếp qua WebAPIs.

WebAPIs sẽ chịu trách nhiệm đếm thời gian (ở đây là 5s).

Cùng lúc đó, lệnh cuối cùng được thực thi trong stack.

Sau khi các lệnh trong stack được thực thi hoàn toàn, kết quả được in lên màn hình và 2 lệnh đó được pop ra khỏi stack.

Sau khi WebAPIs hoàn tất việc đếm thời gian (5s). Callback chứa tập lệnh của setTimeout được gửi đến phòng chờ task queue.

Lúc này mới đến nhiệm vụ của event loop, nó sẽ luôn kiểm tra stack xem có trống ko. Và chỉ khi stack trống trơn sạch sẽ, tập lệnh trong callback của setTimeout mới được event loop đẩy lên stack để thực thi ...

... và cuối cùng, chuỗi 'there' được in lên màn hình.

Kết

Đến đây chắc các bạn có thể tự giải thích được bí ẩn setTimeout 0s mà code vẫn chạy sau cùng rồi đúng ko. Chính là câu mình in đậm bên trên đó. Đến đây hi vọng các bạn đã hiểu rõ hơn về event loop nói riêng và khái niệm non-blocking nói chung.

Tham khảo:

  • https://www.youtube.com/watch?v=8aGhZQkoFbQ
0