Event Loop và Bất đồng bộ trong JavaScript



  • Có thể bạn đã biết, Javascript là một ngôn ngữ lập trình đơn luồng và có thể chạy bất đồng bộ. Ở bài viết này chúng ta tìm hiểu một khái niệm khá chuyên sâu và cần thiết cho các JS Dev là Event Loop và cách hoạt động bất đồng bộ trong Javascript. Nếu bạn chưa nắm rõ Javascript là gì thì hãy đọc bài Tổng quan về JS này để hiểu thêm trước khi quay lại bài này nhé.

    Let’s get started !

    event-loop-bat-dong-bo-javascript.jpg

    Call stack & Memory heap

    Trong vấn đề quản lý bộ nhớ thì bạn chắc chắn sẽ được nghe đến Stack và Heap. Bộ nhớ stack và heap bản chất chúng đều là vùng nhớ được tạo ra và lưu trữ ở RAM để thực thi chương trình. Tuy nhiên điểm khác biệt cơ bản nhất bạn cần nắm là:

    • Heap: chịu trách nhiệm chính trong việc lưu trữ, cấp phát dữ liệu, con trỏ, … heap có dung lượng lớn và không cố định.
    • Stack (Call stack): chịu trách nhiệm cấp phát bộ nhớ tham số các hàm, biến cục bộ (local variables). Và cơ chế hoạt động của Stack là Last In First Out (Vào sau ra trước). Bộ nhớ Stack là cố định và không lớn, nếu vượt quá nó thì sẽ được gọi với cái tên quen thuộc Stack overflow 😂

    call-stack.jpg

    Single Thread và Blocking

    Javascript là một ngôn ngữ đơn luồng (single thread) tức là nó chỉ có một call stack, một heap và chạy một thứ duy nhất trong một thời điểm 1️⃣

    Blocking là trạng thái mà bạn không thể thực hiện bất kỳ thao tác nào trên trình duyệt (click, bôi đen, gõ input, …) do stack vẫn còn chạy một hàm nào đó.

    Stack trace và Stack overflow

    Stack trace (dấu vết của stack) nó sẽ ghi nhận lại con đường từ hàm main đến hàm vừa chạy. chạy thử đoạn code sau, bạn sẽ gặp một báo lỗi rất quen thuộc mà ta vẫn hay gặp.

    stack-trace.jpg

    Stack Overflow (Tràn bộ nhớ stack) thường xãy ra khi chúng ta chạy các hàm đệ quy nhưng không có điểm dừng hoặc các giá trị vượt quá ngưỡng của stack.

    Event Loop

    Do JS đơn luồng nên nó chỉ xử lý được duy nhất một tác vụ trong cùng một thời điểm, điều này rất khác với các ngôn ngữ đa luồng (multi-thread) khác như C#, Java, PHP với mỗi tác vụ thì nó sẽ chia ra một luồng để xử lý.

    Nếu như thế thì JS rất dễ rơi vào tình trạng Blocking nếu gặp phải các tác vụ mất nhiều thời gian như xử lý nhiều request, call APIs, … Event Loop ra đời để giải quyết vấn đề này, khiến JS chỉ đơn luồng như cân tất nhiều tác vụ cùng lúc 😎

    Để tìm hiểu về Event Loop thì chúng ta cần biết thêm 2 khái niệm đó chính là:

    • Web APIs: bản chất Runtime của Javascript chỉ có 1 luồng và không thể chạy multi-thread, vì thế browser đã viết thêm một Web APIs để bọc runtime này lại (tương tự dưới NodeJS sẽ dùng C++ để bọc V8 lại). Web APIs này sẽ giúp cho JS có thể hoạt động một cách bất đồng bộ như multi-thread.

    • Callback Queue: Như tên của nó là hàng đợi các callback do thằng Web APIs ở trên trả về.
      event-loop.jpg

    Cách thức hoạt động bất đồng bộ trong Javascript

    Khi hàm main được chạy thì các đoạn code trong main sẽ được thực thi. Nó sẽ lần lượt đẩy các hàm vào bên trong call stack theo nguyên tắc LIFO.

    Các hàm hay tác vụ liên quan đến Events (click, change, listener, …), AJAX (Call APIs), Timing (setTimeout, setInterval) sẽ được đẩy từ call stack sang Web APIs. Còn lại thì sẽ được thực thi trong call stack đến khi nào xong thì pop nó ra cho hàm bên dưới được thực thi.

    Ở Web APIs sẽ tận dụng các nhân của thiết bị để xử lý riêng biệt các tác vụ này. Sau khi hoàn tất thì Web APIs sẽ trả về một callback và đẩy nó vào trong Callback Queue.

    Callack Queue hoạt động theo nguyên tắc của queue là FIFO (vào trước ra trước) không như stack.

    Event loop hiểu nôm na là một vòng lặp vô tận, nó luôn trực chờ ở đó để quan sát bé Callback Queue và bé Call stack.

    Bất kể khi nào mà call stack trống (tất cả các hàm được pop ra) thì nó sẽ nắm các thằng callback ở trong Callback Queue và ném vào trong Call Stack để tiếp tục thực thi.

    while (queue.waitForMessage()) {
      queue.processNextMessage()
    }
    

    Và cứ thế tụi nó sống chan hoà với nhau đến cuối đời 🤣

    Code Demo

    $.on('button', 'click', function onClick() {
        setTimeout(function timer() {
            console.log('You clicked the button!');    
        }, 2000);
    });
    
    console.log("Hi!");
    
    setTimeout(function timeout() {
        console.log("Event Loop");
    }, 2000);
    
    setTimeout(function timeout2() {
        console.log("Bat dong bo");
    }, 0);
    
    console.log("Dyno");
    

    Kết quả đoạn code trên: Hi, Dyno, Bat Dong Bo, Event Loop

    alt text

    Giải thích:

    • Event listener ‘click button’ sẽ được đưa vào call stack, vì nó là event nên được đẩy qua Web API và ở đó lắng nghe người dùng click nút thì chạy hàm onClick.
    • console.log(“Hi”) được push vào stack, chạy hàm log ra “Hi” và pop ra khỏi stack.
    • setTimeout với callback timeout() được push vào stack, sau đó đẩy nó qua Web APIs và chạy song song bên đó, sau 2 giây Web API sẽ đẩy nó xuống callback queue.
    • setTimeout với timeout2() có thời gian là 0, nhưng không có nghĩa là callback của nó sẽ được chạy ngay lập tức mà nó vẫn được đẩy qua web APIs và được đưa ngay xuống callback queue.
    • console.log(“Dyno”) được push vào stack, chạy hàm log ra “Dyno” và pop ra khỏi stack.
    • Lúc này call stack đã trống, event loop sẽ đẩy các callback trong callback queue lên call stack để thực thi.

    Tạm kết

    Trên đây là tổng quan về Event loop và cách hoạt động bất đồng độ trong JavaScript, hiểu được những điều này sẽ cải thiện cái nhìn rất nhiều cho chúng ta về JavaScript. Tuy nhiên để hiểu sâu hơn nữa thì chúng ta cần phải luyện tập nhiều hơn để gặp những case study lạ lẫm khác của Javascript 😂

    Cảm ơn mọi người đã đọc bài viết này ❤

    Nguồn bài viết: https://dynonguyen.com/event-loop-bat-dong-bo-trong-javascript/


Log in to reply
 

});