4 điểm bởi GN⁺ 2024-03-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong cộng đồng Rust, thường xuất hiện câu hỏi: thread có thể làm được mọi thứ mà async/await làm được và còn đơn giản hơn, vậy tại sao lại chọn async/await?
  • Rust là ngôn ngữ cấp thấp, không che giấu độ phức tạp của coroutine. Đây là khái niệm đối lập với các ngôn ngữ như Go, nơi mọi thứ về cơ bản trở thành bất đồng bộ mà lập trình viên gần như không cần phải nghĩ đến bất đồng bộ.
  • Lập trình viên giỏi luôn cố tránh sự phức tạp, vậy tại sao lại cần async/await?

Tìm hiểu bối cảnh

  • Rust là ngôn ngữ cấp thấp. Mã thường mang tính tuyến tính: một tác vụ kết thúc rồi tác vụ khác mới chạy.
  • Trong những trường hợp cần chạy đồng thời nhiều tác vụ như máy chủ web, mã tuyến tính sẽ gây ra vấn đề.
  • Web thời kỳ đầu đã cố giải quyết vấn đề này bằng cách đưa threading vào.
  • Có thể dùng thread để xử lý nhiều client cùng lúc, nhưng các lập trình viên muốn đưa tính đồng thời từ không gian của OS sang không gian người dùng.

Vấn đề timeout

  • Một trong những ưu điểm lớn nhất của Rust là tính khả hợp (composability).
  • async/await cho phép áp dụng tính khả hợp này cho các hàm bị ràng buộc bởi I/O.
  • Ví dụ, khi muốn thêm timeout vào một hàm xử lý client, có thể triển khai bằng hai combinator.

Thread theo chủ đề

  • Trong ví dụ dùng thread, việc triển khai timeout không hề dễ dàng.
  • TcpStream có các hàm set_read_timeoutset_write_timeout, nhưng cách dùng chúng khá hạn chế.
  • Bài viết đưa ra cách lập trình timeout bằng combinator của Rust, nhưng cách này chỉ giới hạn ở TcpStream và cần thêm các system call.

Các trường hợp async thành công

  • Hệ sinh thái HTTP đã chấp nhận async/await làm cơ chế runtime chủ đạo.
  • tower là ví dụ cho thấy sức mạnh của async/await, với các tính năng như timeout, giới hạn tốc độ và cân bằng tải.
  • macroquad là game engine Rust chạy engine bằng async/await.

Cải thiện hình ảnh của async

  • Lợi ích của async chưa được biết đến rộng rãi nên một số người có thể hiểu sai.
  • Cộng đồng Rust có xu hướng đánh giá quá cao lợi thế hiệu năng của async Rust và xem nhẹ những lợi ích thực sự có ý nghĩa của nó.
  • async/await nên được xem là một mô hình lập trình mạnh mẽ có thể diễn đạt ngắn gọn những mẫu mà trong Rust đồng bộ phải cần đến hàng chục thread và channel mới biểu đạt được.

Ý kiến của GN⁺

  • async/await làm tăng độ phức tạp của mã khi xử lý tính đồng thời, nhưng đồng thời mang lại khả năng xử lý hiệu quả rất nhiều client cùng lúc.
  • Bài viết nhấn mạnh rằng async/await không chỉ đơn thuần có lợi thế về hiệu năng mà còn có thế mạnh ở mô hình lập trình.
  • async/await trong Rust mang lại tính khả hợp cho nhiều tác vụ I/O khác nhau, đặc biệt hữu ích trong các lĩnh vực như dịch vụ mạng hoặc máy chủ web.
  • Nhìn từ góc độ phê bình, độ phức tạp của async/await có thể trở thành rào cản gia nhập với lập trình viên mới, và cần có nỗ lực giáo dục để vượt qua điều đó.
  • Các dự án khác cung cấp chức năng tương tự gồm phần triển khai async/await của Node.js và thư viện asyncio của Python; chúng cũng cung cấp một mô hình tương tự.
  • Khi đưa async/await vào sử dụng, cần cân nhắc độ phức tạp và khả năng bảo trì của mã; tuy nhiên, nếu phải xử lý đồng thời rất nhiều client thì mô hình này mang lại lợi thế lớn.

1 bình luận

 
GN⁺ 2024-03-26
Ý kiến trên Hacker News
  • Async/await và luồng đơn

    • async/await trên luồng đơn, như mô hình của JavaScript, khá đơn giản và đã được hiểu rõ.
    • Khi dùng thread, nhiều CPU có thể cùng xử lý vấn đề, và Rust hỗ trợ quản lý lock.
    • Có thể có các thread với mức ưu tiên khác nhau, và điều này cần thiết khi việc tính toán bị giới hạn.
    • async/await đa luồng thì phức tạp. Trong các phần bị giới hạn bởi tính toán, mô hình này có thể sụp đổ.
    • Tính toán đa luồng trong Rust không hoạt động tốt. Các vấn đề gồm:
      • Sụp đổ do tranh chấp futex: có thể là vấn đề với một số allocator lưu trữ.
      • Bỏ đói do mutex không công bằng: Mutex tiêu chuẩn và channel crossbeam-channel đều không công bằng.
  • Async/await so với thread

    • Chỉ trích ở đây không phải về độ phức tạp, mà là việc hệ sinh thái bị chia tách theo lựa chọn và một bên trở nên kém hơn.
    • Hệ sinh thái Rust đã quyết định rằng nếu muốn làm tác vụ I/O thì phải dùng toàn bộ async/await.
    • Nếu Rust biến những lựa chọn ngoài async/await thành các abstraction có khả năng kết hợp tốt hơn, thì sự bất mãn đã biến mất.
  • Vấn đề của bài viết

    • Chỉ đưa ra một ví dụ về web server, và cách giải cho thread cũng không đúng.
    • Lập trình viên muốn các thread mang tính khái niệm, ngữ nghĩa, chứ không phải OS thread.
    • OS thread có chi phí cao, còn chúng ta muốn thread rẻ.
    • Có vấn đề trong cách triển khai timeout ở ví dụ web server.
  • Những điểm chưa được đề cập

    • async/await chạy trên luồng đơn nên không cần lock hay đồng bộ hóa.
    • Việc truyền lỗi trong async/await không rõ ràng.
    • Backpressure trong network I/O cũng nên được nhắc đến.
  • Điểm quan trọng về hủy bỏ

    • Có thể dễ dàng hủy bất kỳ tác vụ nào trong tương lai.
    • Việc hủy trong thread thì phức tạp, và ép dừng thread là không đáng tin cậy.
    • Trong mô hình async của Rust, có thể thêm timeout từ bên ngoài cho mọi future.
  • Chiến dịch như marketing cho async/await

    • async/await là một sai lầm về mặt kỹ thuật, và đã gây ra chi phí lớn cho cộng đồng.
    • Rust vẫn là ngôn ngữ tốt nhất, nhưng vẫn lo rằng cuộc tranh luận này sẽ kéo dài mãi mãi.
  • Async/await so với fiber

    • Rust trước đây từng có green thread và đã chủ động loại bỏ chúng.
    • Khả năng drop future bất cứ lúc nào đi kèm với cái giá rất lớn.
    • Việc ca ngợi tính composability của async/await là điều kỳ lạ.
  • Lợi ích chính của async/await trong Rust

    • Có thể hoạt động ngay cả trong môi trường không có thread hay bộ nhớ động.
    • Có thể dùng tính đồng thời để viết mã ngắn gọn.
  • Những hiểu lầm về async/await

    • Có người không hiểu vì sao cần cơ chế đồng thời trên một luồng đơn.
    • async/await hữu ích cho lập trình UI, giao tiếp với GPU, và giao tiếp giữa các runtime.
  • Lý do chọn async/await thay vì thread

    • async/await có thể giảm mức sử dụng bộ nhớ cho trạng thái client/request/task.
    • Việc nén trạng thái rất quan trọng với hiệu năng trong thế giới hiện đại nơi tốc độ bộ nhớ chậm.
    • async/await và CPS hiệu quả trong việc giảm lượng bộ nhớ dùng cho mỗi client.