JavaScript là 1 ngôn ngữ đơn luồng, vậy thì làm sao nó có thể xử lý nhiều request cùng 1 lúc
Frontend Dzui Lắm
Tác giả

Chắc hẳn khi làm việc với JavaScript trên giao diện (như React/Vue) hay phía server (như Node.js), bạn đã nghe câu thần chú quen thuộc: "JavaScript là ngôn ngữ đơn luồng (single-threaded)".
Đơn luồng nghĩa là tại một thời điểm, nó chỉ có thể làm duy nhất MỘT việc. Giống như một quán cafe chỉ có đúng 1 nhân viên: vừa nhận order, vừa pha cafe, vừa dọn bàn. Vậy làm thế nào mà Node.js hay Browser có thể xử lý hàng ngàn request gọi API, render UI và lắng nghe sự kiện click chuột cùng một lúc mà không bị "treo"?
Bí mật nằm ở một cơ chế thiên tài: Event Loop.

1. Bức tranh toàn cảnh: Call Stack (Ngăn xếp lệnh)
Bản thân engine V8 của JavaScript thực sự chỉ có 1 luồng chính (Main Thread) và 1 Call Stack.
Call Stack hoạt động giống như một chồng đĩa. Hàm (function) nào được gọi sẽ bị đặt lên trên cùng của chồng đĩa. Hàm nào chạy xong sẽ được lấy ra khỏi chồng.
Vì chỉ có 1 Call Stack, nếu bạn có một hàm chạy mất 10 giây (như một vòng lặp for khổng lồ xử lý dữ liệu nặng), toàn bộ hệ thống sẽ bị "đóng băng" trong 10 giây đó. Không ai có thể click vào nút nào, không request nào được phản hồi. Đây gọi là hiện tượng Blocking.
2. Những "Người giúp việc tàng hình": Web APIs / C++ APIs
Nếu JavaScript chỉ có 1 luồng, vậy ai là người thực hiện các tác vụ tốn thời gian như đếm ngược setTimeout, gọi API bằng fetch, hay đọc file?
Câu trả lời là: Không phải bản thân JavaScript!
Khi JavaScript gặp một tác vụ bất đồng bộ (async), nó sẽ "đẩy" tác vụ đó cho môi trường bên ngoài xử lý:
Trong trình duyệt: Đó là các Web APIs (như DOM, AJAX, Timeout).
Trong Node.js: Đó là các C++ APIs (thông qua thư viện libuv).
Các môi trường này là Đa luồng (Multi-threaded). Chúng nhận việc, chạy ngầm ở background, và trả lại cho JavaScript một luồng chính rảnh rỗi để tiếp tục đọc các dòng code tiếp theo ngay lập tức.

3. Nơi tập kết kết quả: Các Hàng đợi (Queues)
Khi các "người giúp việc" ở Web APIs làm xong việc (ví dụ: API đã tải xong dữ liệu), họ không thể ném thẳng kết quả trở lại Call Stack đang bận rộn. Làm vậy sẽ gây crash chương trình. Thay vào đó, họ xếp kết quả (các hàm callback) vào hàng đợi:
Macrotask Queue (Callback Queue): Dành cho
setTimeout,setInterval, DOM events...Microtask Queue (Priority Queue): Dành riêng cho
Promises(.then,.catch),queueMicrotask.
Luật bất thành văn: Microtask Queue là "khách VIP". Nó luôn được ưu tiên giải quyết sạch sẽ tất cả các tác vụ bên trong trước khi hệ thống đụng đến bất kỳ ai đang đứng đợi ở Macrotask Queue.
4. Trái tim của hệ thống: Event Loop (Vòng lặp sự kiện)
Đây là nhân vật chính của chúng ta. Event Loop thực chất là một vòng lặp vô tận với một nhiệm vụ duy nhất: Liên tục theo dõi Call Stack và Các hàng đợi.
Cách nó làm việc cực kỳ đơn giản theo các bước:
Nhìn vào Call Stack. Có đang rảnh không? (Chống rỗng chưa?).
Nếu rảnh, nhìn sang Microtask Queue. Có ai đang đợi không? Nếu có, bế từng người một vào Call Stack cho đến khi Microtask Queue trống rỗng hoàn toàn.
Nếu Microtask Queue đã trống, nhìn sang Macrotask Queue. Lấy ĐÚNG MỘT tác vụ đầu tiên ra và đưa vào Call Stack để chạy.
Lặp lại bước 1.

Tóm lại bằng một ví dụ thực tế
Hãy tưởng tượng toàn bộ hệ thống JavaScript là một quán ăn:
Main Thread: Là bạn - người bồi bàn duy nhất của quán.
Call Stack: Khả năng phục vụ của bạn tại một thời điểm (chỉ tiếp được 1 bàn).
Web APIs: Đội ngũ đầu bếp đông đảo trong bếp.
Task Queue: Quầy nhận món ăn đã nấu xong.
Event Loop: Thói quen liên tục quan sát của bạn: Cứ mỗi khi rảnh tay (không phải tiếp khách nào), bạn lại nhìn ra quầy nhận món xem có món nào nấu xong chưa để bưng đi.
Khi khách gọi món phức tạp (như gọi API fetch dữ liệu), bạn không đứng chết trân ở đó chờ đầu bếp nấu xong. Bạn gửi order vào bếp (giao cho Web APIs), rồi lập tức quay ra nhận order của bàn khác. Khi đầu bếp nấu xong, họ đặt món lên quầy. Bất cứ khi nào bạn rảnh (Call Stack trống), bạn sẽ tự động ra quầy lấy món đi giao.
Đó chính là cách một ngôn ngữ "đơn luồng" có thể phục vụ hàng ngàn request mà không hề đổ mồ hôi!
Thích bài viết này?
Nội dung trên Vết Mực luôn được chia sẻ miễn phí. Nếu bài viết mang lại giá trị cho bạn, hãy cân nhắc ủng hộ để chúng mình có thể duy trì máy chủ, phát triển thêm tính năng mới và tiếp tục xây dựng một không gian dành cho những người yêu viết lách. ✨
Các cách ủng hộ:
- •Viết và đăng bài trên Vết Mực
- •Chia sẻ bài viết với bạn bè
- •Góp ý để chúng mình cải thiện sản phẩm qua email: nsikhoa@gmail.com
Dù bạn chọn ủng hộ hay chỉ đơn giản là tiếp tục đọc và chia sẻ bài viết, đó đều là nguồn động lực rất lớn với chúng mình. ❤️
Bình luận
Đăng nhập để để lại bình luận.