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

Threads
  • Có bạn nào trong lúc code mà bị "ngẫn tò te" hoặc "lú lẫn" trong mớ hỗn độn của mình viết ra chưa. Những lúc như thế tôi thường hay ngừng lại, thư giãn đầu óc, hoặc để mai tính tiếp. Ấy vậy mà rất hiệu quả nhé! Lúc sau xem lại cái tự nhiên thấy vấn đề ở đâu ngay.

    Hoặc cũng có thể thử áp dụng phương pháp "chú vịt vàng" - thử giải thích vấn đề đang gặp phải với vịt xem, nó sẽ cho thấy mâu thuẫn trong lời nói ra.

    Còn không có tiền mua vịt to thì xe túi mù ra vịt bé giống mình nè 😅

    » Xem thêm
  • Ơ buồn cười thật. Ai cũng biết GIF là định dạng ảnh động thường được dùng thay cho video clip để hiển thị các nội dung ngắn trên nền tảng web. GIF tiện hơn các nội dung dạng video là vì nó được hiển thị như một bức ảnh và được hỗ trợ rộng rãi. Cơ mà GIF có dung lượng nặng quá.

    Nói thật nhiều lúc mình có vài cái hành động muốn hiển thị lên web cho mọi người xem, cơ mà định dạng GIF nó nặng với cả cũng không biết cách tối ưu cho nhẹ xuống. Hôm nay lên mạng tìm hiểu xem định dạng nào có khả năng thay thế GIF trong tương lai thì mọi người biết đó là gì không? Là WEBP (webp)!!! Đúng vậy, là định dạng ảnh mà mình đang dùng trên blog lâu nay luôn á, mà giờ mới biết là nó hiển thị được cả ảnh động nữa, hơi quê 😆

    Kết hợp với ffmpeg nữa là chuyển được tất tần tật video clip thành webp được ngay. Để vài nửa ngồi chế lại cái cli một tí là dùng ngon luôn mọi người ạ 🤪

    » Xem thêm
  • Cảm giác như Github Copilot đang cố gắng mở rộng thị trường cho anh em developer á. Mới trước họ ra mắt Github Open Copilot Chat thì mới đây lại thêm cái Using GitHub Copilot in the command line dùng để giải thích hoặc gợi ý lệnh trong terminal.

    Đây, cách dùng rất đơn giản thôi, ví dụ muốn nó giải thích câu lệnh sudo apt-get để làm gì, thì:

    $ gh copilot explain "sudo apt-get"

    Hoặc nhờ nó gợi ý lệnh mong muốn, sử dụng tiếng Việt được luôn nhé (kể cả tiếng Việt không dấu vẫn hiểu 😳)

    $ gh copilot suggest "xoá commit chưa push"

    Mình đã kiểm tra và thấy lệnh ra rất đúng, xịn thật 🤓

    » 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 Node.js 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 nhất cho các ứng dụng thiên về khả năng khai thác triệt để sức mạnh 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 không. Bài viết ngày hôm nay tôi sẽ dành thời gian nói về vấn đề nêu trên.

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 với 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 độ xử lý của CPU như tốc độ đọc/ghi của ổ đĩa, tốc độ của mạng... Nếu I/O được xử lý trên V8 nó sẽ gây ra một cuộc tắc nghẽn nghiêm trọng vì mất quá nhiều thời gian, chính vì thế Node.js phải tìm ra một giải pháp để 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à lựa chọn libuv cho xử lý I/O không đồng bộ. Đây là thư viện C đa nền tảng cung cấp hỗ trợ cho I/O không đồng bộ dựa trên các vòng lặp sự kiện (Event Loop).

V8 + libuv Node.js

Luồng chính gặp các tác vụ I/O sẽ đẩy chúng xuống libuv, kết quả được đưa trở lại cho luồng chính thông qua Event Loop. Cứ như thế tạo nên một vòng lặp vô hạn mà luồng chính không bị chặn bởi I/O nữa.

Trong 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 và chờ nhận phải hồi, 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ác task 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 những giải pháp này còn phụ thuộc vào sức mạnh của phần cứng.

Tổng kết

Với 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 I/O thế nên Node.js hoàn toàn phù hợp với những bài toán thiên về I/O. Thế nhưng không phải là Node.js không xử lý được những bài toán nặng về tính toán. Bằng cách tạo ra những child process hoặc worker threads trên CPU đa lõi hoàn toàn có thể xử lý mà không chặn luồng chính.

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.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

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