[ Javascript ] Hiểu rõ cơ chế xử lí đồng bộ và bất đồng bộ (Sync vs Async) trong Javascript – P1

       Chúng ta đã nhiều lần nghe nói tới khái niệm xử lí đồng bộ (synchronous)xử lí bất đồng bộ (asynchronous) – tạm gọi ngắn gọn là sync và async. Chúng ta cũng có thể viết code để xử lí theo 2 kiểu đó, code chạy được, nhưng còn việc thực sự hiểu cơ chế xử lí bên trong thì như thế nào?

        Một cách nôm na, có thể hiểu xử lí đồng bộ là code sẽ được chạy tuần tự theo trình tự đã viết sẵn – tức là đoạn code ở dưới phải đợi cho tới khi đoạn code ở trên trả ra kết quả. Ngược lại, xử lí bất đồng bộ là những xử lí mà đoạn code ở dưới có thể tiếp tục chạy mặc dù đoạn code ở trên chưa được xử lí hết và trả về kết quả. Nghe có vẻ đơn giản, phải không???

      Nếu hiểu cách đơn giản như trên, có lẽ chúng ta sẽ cho rằng tất cả những kiểu viết hàm thông thường sẽ là xử lí đồng bộ, chỉ những thứ đặc biệt như setTimeout() hay AJAX mới là xử lí bất đồng bộ. Như vậy có đúng không? Thực ra thì … không hẳn như vậy, bởi xét cho cùng thì tất cả những thứ trên đều có cùng 1 cơ chế xử lí như nhau. Hãy xem thử nó ngay sau đây nào.

1. Một minh hoạ cho xử lí đồng bộ (Synchronous processing)

      Chúng ta hãy liên hệ xử lí đồng bộ với 1 việc trong thực tế để có cái nhìn rõ hơn, bạn còn nhớ tới Giáo sư Cù Trọng Xoay (gọi là Mr. X) không nào, hãy chú ý 1 vài yếu tố sau:

  • Mr. X là 1 người thông thái, có thể giải quyết và trả lời mọi việc.
  • Bạn muốn nhờ Mr. X trả lời hoặc giải quyết điều gì, hãy gọi điện trực tiếp cho ông ấy (chú ý rằng đây là cách liên lạc duy nhất).
  • Bất cứ khi nào nhận được yêu cầu, Mr. X sẽ giải đáp ngay lập tức.
  • Như vậy, muốn giải quyết gì thì cứ nhấc máy gọi Mr. X và nghe giải đáp ngay tức khắc, thế là công việc được xử lí xong.

      Đây có lẽ là 1 hình dung rõ ràng nhất về việc xử lí đồng bộ (xử lí tuần tự) trong thực tế: Đầu tiên, bạn hỏi, Mr. X lắng nghe, sau khi nhận yêu cầu thì bạn lắng nghe câu trả lời từ Mr. X, xong!!! Nhưng nếu vậy thì vấn đề phát sinh ở chỗ nào???

sync vs async 3
Sẽ thế nào nếu có quá nhiều người cùng gọi cho Mr. X?

2. Khi xử lí đồng bộ gặp vấn đề

       Bạn biết đấy, Mr. X (Giáo sư Xoay) rất thông thái, việc lớn nhỏ to bé gì cũng giải quyết được ráo, khi đó thì số lượng người hỏi sẽ ngày càng nhiều, và vấn đề phát sinh: khi bạn gọi điện thoại cho Mr. X thì nhận được thông báo “máy bận”. Để tránh tình trạng bạn phải gọi lại nhiều lần mới gặp được giáo sư, thì giáo sư Xoay đổi cách giải quyết: thay vì gọi trực tiếp, giáo sư sẽ nhận và trả lời thông qua tin nhắn:

  • Giáo sư thuê anh Xoáy (gọi là Mr. M) để quản lí việc nhận tin nhắn (message). Các tin nhắn gửi tới Mr. X sẽ được sắp xếp theo đúng thứ tự gửi.
  • Mọi yêu cầu xử lí bây giờ sẽ được gửi tới anh Xoáy (Mr. M), nhiệm vụ của Mr. M là quản lí messages và lần lượt đưa từng message cho Mr. X để nhờ giải quyết, cứ thế lần lượt đưa thêm từng xử lí một cho Mr. X khi message cũ đã giải quyết xong.
  • Như vậy, một khi Mr. X tiếp nhận tới yêu cầu của bạn gửi, thì tên của bạn sẽ được nhắc tới (được gọi tới) để bạn có thể lắng nghe câu trả lời.

       Như vậy liệu bạn sẽ đặt câu hỏi: kiểu này là đồng bộ hay bất đồng bộ đây? Câu trả lời là: cả 2.

      Khi bạn gửi yêu cầu, yêu cầu của bạn chưa được xử lí ngay, bạn chưa nhận được kết quả nhưng bạn vẫn có thể tiếp tục gửi tin nhắn khác, đó là bất đồng bộ ở chiều gửi yêu cầu. Tuy nhiên, khi Mr. X đang trả lời 1 message của bạn, và lúc đó bạn cũng đang lắng tai nghe, thì đó xử lí đồng bộ trong việc giải đáp từng message.

      Minh hoạ vừa rồi đã giúp bạn hình dung được nhiều khái niệm cốt lõi, cơ chế xử lí Async và Sync trong Javascript cũng rất tương đồng với minh hoạ ở trên. Hãy xem chi tiết hơn ngay sau đây.

3. Javascript – Một ngôn ngữ của xử lí bất đồng bộ

     Khi ai đó nói rằng Javascript là một ngôn ngữ xử lí bất đồng bộ, thì rất có thể là họ đang nói tới cơ chế xử lí theo dạng gửi message (như minh hoạ phía trên). Thật vậy, trong Javascript, hàm (function) sẽ không bao giờ được gọi xử lí trực tiếp, nó sẽ được giải quyết thông qua các message.

       Bộ máy xử lí bên trong của Javascript gồm có các phần chính như sau: một hàng đợi tin nhắn (message queue), một vòng lặp event (event-loop) và một stack xử lí (call stack). Message queue tiếp nhận các xử lí được gọi tới, mỗi hàm được gọi sẽ tương ứng là 1 message trong hàng đợi. Event-loop làm nhiệm vụ điều phối việc lấy các message đang có trong queue: mỗi lần sẽ lấy 1 message ra khỏi queue để đem đi xử lí, trong quá trình xử lí thì event-loop sẽ đứng chờ cho tới khi message cũ được xử lí xong, sau đó sẽ tiếp tục lấy message mới trong queue để xử lí, và cứ lặp lại như thế.

     Vậy call-stack làm gì? Call-stack là 1 ngăn xếp quản lí việc thực thi hàm và gọi lồng hàm (nested function), tức là: Với mỗi hàm vừa lấy ra khỏi queue để đem ra xử lí, thì nó sẽ tương ứng với 1 frame ở trong call-stack, nếu bên trong hàm này có gọi tới nhiều hàm con nữa, thì những hàm con này sẽ được thêm tiếp vào call-stack dưới dạng 1 frame mới của stack. Và Javascript sẽ lần lượt xử lí call-stack này theo dạng first-in last-out (xử lí dạng stack), cho đến khi stack rỗng thì coi như message đó đã được xử lí xong.

       Cơ chế này giống với minh hoạ mình đã nói ở phần trên, hình vẽ sau sẽ giúp bạn hình dung rõ hơn về điều này:

message queue 1

        Có thể thấy rằng, với mỗi message đang đợi ở trong queue, nó sẽ phải đợi cho tới khi call-stack hoàn toàn rỗng thì Event-loop mới thực hiện việc lấy message khác trong queue ra xử lí, và message mới lấy ra sẽ tương ứng với 1 frame mới trong call-stack.

        Đoạn code dưới đây sẽ cho các bạn thấy cụ thể việc này như thế nào:

function foo ()
{
console.log("foo");
}

function bar ( )
{
console.log("bar");
foo();
}

function baz ()
{
console.log("baz");
}

////Execute function to test Messages queue & call stack
bar( );
baz();

       Khi chạy đoạn code trên, sẽ có 2 message được gửi đến Message queue tương ứng với yêu cầu xử lí của hàm bar()baz(), message tương ứng với bar() sẽ đứng trước. Khi message của bar() được Event-loop đem ra xử lí, thì call-stack sẽ có 1 frame là bar, hàm này tiếp tục gọi tới hàm foo() do đó call-stack thêm 1 frame mới của foo vào, frame này sẽ ở trên top theo đúng quy tắc của stack.

     Sau khi xử lí lần lượt 2 frames của foobar xong, lúc này call-stack sẽ trống, khi đó Event-loop sẽ lấy tiếp message của hàm baz() để đưa vào call-stack xử lí. Xem hình GIF sau để hiểu quá trình vừa chạy:

call-stack.gif

      Nhớ nhé, hàm sẽ không được gọi trực tiếp trong Javascript, thay vào đó là cơ chế gởi message. Khi nghe nói đến việc Javascript là 1 ngôn ngữ chạy bất đồng bộ, hãy nhớ rằng đó là đang ám chỉ tới cơ chế quản lí và xử lí Message queue ẩn bên trong Javascript. Hàm sẽ không được kích hoạt ngay khi gọi mà phải đợi trong Message queue, khái niệm bất đồng bộ là ở chỗ đó.

Vậy thì những hàm bất đồng bộ (async function) thì thế nào?

        Đến đây có vẻ như bạn vẫn còn có thể thắc mắc rằng liệu các hàm “có vẻ” bất đồng bộ như setTimeout() hoặc AJAX thực chất là gì, và có khác gì cơ chế ở trên không? Câu trả lời là: cả 2 cũng sẽ vẫn tuân theo cơ chế ở trên.

       Tuy nhiên, mình sẽ không vội nói tới ngay về những thứ đó ở đây, bạn cần phải thực sự hiểu cơ chế hoạt động của Message queue, về sự đồng bộ và bất đồng bộ đã được phân tích ở trên trước khi tìm hiểu sâu hơn.

      Mình sẽ tiếp tục bàn về Mr. X và các hàm async ở bài kế tiếp nhé, chúc vui vẻ 🙂

Vcttai

Tham khảo:

  1. Understanding Synchronous and Asynchronous in JavaScript
  2. What is the difference between synchronous and asynchronous programming (in node.js)

10 thoughts on “[ Javascript ] Hiểu rõ cơ chế xử lí đồng bộ và bất đồng bộ (Sync vs Async) trong Javascript – P1

  1. Pingback: [ Javascript ] Hiểu rõ cơ chế xử lí đồng bộ và bất đồng bộ (Sync vs Async) trong Javascript – P2 – Những dòng code vui

  2. Nam

    Tiêu đề bạn để là xử lí đồng bộ và bất đồng bộ (Sync vs Async) trong Javascript vậy mà trong bài bạn lại ghi là Javascript – Một ngôn ngữ của xử lí bất đồng bộ là sao??

    Liked by 1 person

  3. kdz

    Đoạn này : Hàm sẽ không được kích hoạt ngay khi gọi mà phải đợi trong Message queue, khái niệm bất đồng bộ là ở chỗ đó.

    Tôi đang hiểu : đây là khái niệm đồng bộ chứ nhỉ

    Like

    1. Khái niệm bất động bộ có nghĩa là khi bạn gọi kích hoạt một hàm nào đó, hàm đó sẽ không thực sự chạy ngay lúc đó mà sẽ chờ cho tới khi được gọi trong message queue.
      Còn nếu đồng bộ, thì khi bạn gọi hàm, hàm đó sẽ chạy ngay lập tức. Bạn cần phân biệt rõ 2 cái này nhé.

      Thân.

      Like

      1. Ngô Văn Chiến

        Vậy cho em hỏi là MỌI hàm khi được gọi thì đều không thực sự chạy ngay mà chờ tới khi được gọi trong message queue đúng không ạ. Vậy thì lúc nào bất đồng bộ cũng xảy ra ạ?

        Like

  4. Trung

    Cho em hỏi là 1 hàm ở trong call stack rồi và bên trong hàm ý có 4 hàm con, thì nó sẽ add luôn 4 frame vào cùng 1 lúc theo thứ tự FILO rồi mới xử lý hay là vừa thêm vừa xử lý vậy ạ. Em cảm ơn.

    Liked by 1 person

    1. Về cơ bản là nó sẽ thực hiện luôn 4 hàm con đó theo thứ tự, tức là thêm luôn vào call-stack. Khi call-stack được xử lí hết thì sẽ tới message (hàm lớn tiếp theo) tiếp theo được xử lí. Chỉ khj nào call-stack có xử lí bất đồng bộ thì nó sẽ tạo ra message mới và nhét vào queue.

      Like

  5. Pingback: Hiểu rõ cơ chế xử lí đồng bộ và bất đồng bộ (Sync vs Async) trong JavaScript – P1 - Trang Chủ

Leave a comment