Phân biệt tác vụ I/O và tác vụ chuyên sâu CPU

Phân biệt tác vụ I/O và tác vụ chuyên sâu CPU

Tin ngắn hàng ngày dành cho bạn
  • Google rất chịu chơi. Mới đây họ lại phát hành tiếp phiên bản 68 trang dành cho Prompt Engineering. Lần này tập trung hơn vào người dùng API.

    Prompt Engineering

    » Xem thêm
  • Đêm qua "go production" mà thế quái nào dính 2 chưởng đau quá mọi người ạ. Đã chọn buổi đêm để làm rồi mà vẫn toát mồ hôi hột.

    Thứ nhất là npm hôm qua bị ngay quả lỗi không cài được packages. Đen thế không biết. Loay hoay một hồi may quá, trước đó chạy dưới máy rồi nên zip node_modules lại rồi đẩy lên server 😂

    Thứ hai là GCP, triển khai ngon lành xong chạy 1 lúc lăn ra chết. Không thể nào ssh vào được, cuối cùng đành chọn cách tạo VM mới rồi cắm các ổ lại thì lại hoạt động bình thường 😩

    » Xem thêm
  • Có đợt Groq khoe rằng họ có thể chạy được mô hình mã nguồn mở của Meta với tốc độ đạt mức hơn 1 nghìn mã thông báo trên giây. Mới đây Cerebras - một dịch vụ tương tự Groq đã nâng mức kỷ lục mới: Hơn 2 nghìn tokens 🫣

    Mọi người cứ tưởng tượng vừa bấm Enter cái nó đã trả lời xong rồi 🥶

    » Xem thêm

Vấn đề

Là một lập trình viên Node.js đã bao giờ bạn nghe đến thế mạnh của nó là xử lý các tác vụ I/O và không đồng bộ? Rằng Node.js không phải là lựa chọn tốt cho các ứng dụng thiên về khai thác triệt để sức mạnh của CPU? Vậy thì tác vụ I/O là gì và tại sao Node.js lại mạnh về I/O? Việc nói Node.js không thực sự tốt với các phép tính lớn là có đúng hay không? Hãy cùng nhau tìm hiểu trong bài viết ngày hôm nay nhé!

Tác vụ I/O là gì?

I/O (Input/Output) đề cập đến sự tương tác của máy tính hay chương trình máy tính với ổ đĩa (disk) và mạng của hệ thống. Ví dụ các hoạt động I/O bao gồm đọc/ghi dữ liệu từ ổ đĩa, thực hiện các yêu cầu HTTP và tương tác với cơ sở dữ liệu. Chúng rất chậm so với việc truy cập bộ nhớ RAM hoặc các phép tính được thực hiện trên CPU.

V8 xử lý mã Javascript như thế nào

Chúng ta biết rằng Node.js sử dụng V8 của Chrome để thực thi mã JavaScript, có điều sức mạnh của V8 cũng phải chào thua trước I/O bởi chúng không hoàn toàn phụ thuộc vào tốc độ CPU. Các tác vụ như đọc/ghi ổ cứng, truy cập bộ nhớ ngẫu nhiên (RAM), tốc độ mạng... nếu đưa vào xử lý trên V8 thì nó sẽ gây ra một cuộc tắc nghẽn nghiêm trọng vì tốn quá nhiều thời gian để chờ đợi. Chính vì thế Node.js phải tìm ra cách nào đó để vừa tận dụng được sức mạnh của V8 mà vẫn phải xử lý được I/O.

Giải pháp của Node là dùng libuv để xử lý I/O không đồng bộ. Đây là thư viện C đa nền tảng hỗ trợ I/O không đồng bộ dựa trên vòng lặp sự kiện (Event Loop).

V8 + libuv Node.js

Nói về quá trình xử lý. Luồng chính khi gặp các tác vụ I/O đẩy chúng xuống libuv, sau khi xử lý xong, kết quả được đưa trở lại luồng chính thông qua Event Loop. Cứ như thế luồng chính không phải chờ đợi I/O nào mà chỉ xử lý kết quả của I/O.

Node.js có khái niệm worker, chúng có nhiệm vụ chuyển các yêu cầu I/O từ luồng chính đến libuv. Trong thời gian đó chúng không phải làm gì khác và có thể bị hệ điều hành huỷ lập lịch (de-scheduled) để cho một worker khác gửi yêu cầu. Do đó các tác vụ I/O được worker chuyển vào từ trước vẫn được xử lý ngay cả khi luồng liên kết không chạy.

Hệ điều hành đã tối ưu các công cụ quản lý tệp và cơ sở dữ liệu cũng được tối ưu hoá cao để xử lý đồng thời nhiều yêu cầu chờ xử lý. Ví dụ như sắp xếp lại thứ tự ưu tiên khi có đồng thời nhiều yêu cầu đọc/ghi dữ liệu vào một tệp.

Khi chạy một ứng dụng Node.js bạn sẽ có một số thread pools chuyên dùng để xử lý các yêu cầu I/O. Nhóm luồng này được tạo bởi libuv. Số lượng mặc định của nó là 4 nhưng có thể tăng lên tối đa 128 thông qua biến môi trường UV_THREADPOOL_SIZE.

cấu trúc libuv

Tác vụ chuyên sâu CPU

Là những công việc đòi hỏi nhiều về khả năng tính toán của CPU. Đó có thể là những phép tính phức tạp về mã hoá/giải mã, xử lý hình ảnh, xử lý video... Các worker cũng có thể chuyển những yêu cầu tính toán phức tạp này, lập lịch và xử lý chúng bên ngoài luồng chính. Nhưng chúng chỉ được xử lý khi worker được lập lịch trên một trong các lõi của CPU. Ví dụ nếu CPU của bạn có 4 lõi và bạn tạo ra 5 worker thì một trong những worker này không được tham gia xử lý, trong khi vẫn phải duy trì một nguồn tài nguyên cho nó (bộ nhớ & lập lịch) gây ra tình trạng lãng phí tài nguyên.

mô hình worker thread

Để hiểu rõ hơn về cách Node.js xử lý công việc tính toán nặng thông qua Worker Threads tôi khuyên bạn nên đọc bài Worker threads là gì? Bạn đã biết khi nào thì sử dụng Worker threads trong node.js chưa?.

Có thể thấy nếu như luồng chính đưa các tác vụ I/O ra libuv, nó sẽ ngăn chặn được một cuộc tắc nghẽn nghiêm trọng. Trong khi đó, các tác vụ thiên về CPU suy cho cùng sẽ chiếm thời gian xử lý. Giải pháp là tạo ra các child process hoặc worker threads nhưng tạo thế nào và tạo bao nhiêu còn phụ thuộc vào sức mạnh của phần cứng.

Tổng kết

Mô hình kiến trúc của Node.js được thiết kế để tận dụng sức mạnh của V8 mà vẫn giải quyết được các tác vụ I/O. Node.js hoàn toàn phù hợp với những bài toán thiên về I/O. Nhưng không phải là Node.js không xử lý được bài toán khác thiên về sức mạnh của CPU. Để tránh chặn luồng chính hãy tạo ra child process hoặc worker threads để xử lý chúng trong một luồng riêng biệt.

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (1)

Nội dung bình luận...
Avatar
Đăng Khoa2 năm trước
Hoá ra v8 để chạy mã js và nó một luồng đúng ko ạ
Trả lời
Avatar
Thành Đỗ2 năm trước
Đúng rồi bác