- Lập trình bất đồng bộ trong Python cho phép nhiều tác vụ I/O cùng tiến hành mà không làm tắc nghẽn lẫn nhau.
async,awaitvà vòng lặp sự kiện. - Sử dụng các công cụ như
asyncio,aiohttpCác trình quản lý ngữ cảnh bất đồng bộ và quá trình lặp bất đồng bộ cho phép mở rộng quy mô mạng và xử lý các khối lượng công việc nặng về API. - Async vượt trội trong xử lý I/O mạng và tập tin nhưng cần được bổ sung bằng xử lý đa luồng hoặc các dịch vụ chuyên dụng cho các tác vụ phụ thuộc vào CPU.
- Các thực tiễn tốt—tránh các cuộc gọi chặn, hạn chế sự đồng thời và xử lý lỗi cho từng tác vụ—là chìa khóa để viết các ứng dụng bất đồng bộ đáng tin cậy.
Lập trình bất đồng bộ trong Python đã từ một chủ đề chuyên biệt trở thành một trong những kỹ năng cốt lõi đối với bất kỳ ai xây dựng các ứng dụng hiện đại, đáp ứng nhanh. Nếu bạn đang làm việc với API web, microservices, bảng điều khiển thời gian thực hoặc bất kỳ loại đầu vào/đầu ra (I/O) nặng nào, có lẽ bạn đã gặp phải tình trạng mã của bạn dành nhiều thời gian chờ đợi hơn là thực hiện công việc thực sự. Đó chính là lúc các kỹ thuật bất đồng bộ phát huy tác dụng.
Thay vì để chương trình của bạn ở trạng thái chờ trong khi đợi mạng, ổ đĩa hoặc dịch vụ bên ngoài, mã bất đồng bộ cho phép bạn chồng chéo các khoảng thời gian chờ đó và giữ cho ứng dụng hoạt động. Trong hướng dẫn này, chúng ta sẽ tìm hiểu sâu về cách hoạt động của async trong Python, những vấn đề mà nó giải quyết, khi nào nó thực sự hữu ích và khi nào nó là công cụ không phù hợp, và chúng ta sẽ cùng xem xét các ví dụ cụ thể sử dụng async. async, await, asyncio và các thư viện bất đồng bộ phổ biến như aiohttp.
Lập trình bất đồng bộ trong Python là gì?
Về bản chất, lập trình bất đồng bộ là một cách cấu trúc mã sao cho nhiều tác vụ có thể tiến triển mà không làm tắc nghẽn lẫn nhau, ngay cả khi chúng cùng chia sẻ một luồng hệ điều hành duy nhất. Theo kiểu lập trình đồng bộ cổ điển, mỗi thao tác kết thúc trước khi thao tác tiếp theo bắt đầu: gọi API, chờ, phân tích phản hồi, rồi mới tiếp tục. Với mã bất đồng bộ, bạn có thể kích hoạt nhiều thao tác chạy dài và để Python tự động chuyển đổi giữa chúng bất cứ khi nào một trong số chúng đang chờ đợi.
Python triển khai mô hình này bằng cách kết hợp cú pháp đặc biệt và bộ lập lịch hợp tác được xây dựng xung quanh một vòng lặp sự kiện. Hai từ khóa mở ra tất cả những điều này là async và awaitBạn đánh dấu các hàm là bất đồng bộ bằng cách sử dụng async defvà bạn dừng lại bên trong chúng với await bất cứ khi nào bạn gặp phải một thao tác có thể trả lại quyền điều khiển cho vòng lặp sự kiện.
An async def Hàm này không trả về giá trị trực tiếp; nó trả về một đối tượng coroutine đại diện cho một phép tính có thể được lên lịch và chờ đợi. Khi bạn sử dụng await Bên trong hàm đó, Python tạm dừng coroutine hiện tại và cho phép các tác vụ đang chờ xử lý khác chạy cho đến khi thao tác được mong đợi (như yêu cầu mạng) hoàn tất, tại thời điểm đó quá trình thực thi sẽ tiếp tục ngay sau đó. await.
Điều này rất quan trọng: mã Python bất đồng bộ thường vẫn là đơn luồng, nhưng đồng thời theo nghĩa là nhiều thao tác được thực hiện trong các khoảng thời gian chồng chéo nhau. Trong khi một tác vụ chờ đợi thao tác I/O, một tác vụ khác sẽ chiếm dụng thời gian CPU. Đó là lý do tại sao lập trình bất đồng bộ rất phù hợp với các tác vụ phụ thuộc vào I/O nhưng không thể tự động tăng tốc các tác vụ nặng về CPU.
Một ví dụ cụ thể: Các cuộc triển lãm cờ vua và thời gian chờ đợi
Một phép so sánh kinh điển được cộng đồng Python sử dụng để giải thích sự khác biệt giữa thực thi song song và thực thi tuần tự xuất phát từ một cuộc thi cờ vua đồng thời. Hãy tưởng tượng một kiện tướng cờ vua thi đấu với 24 kỳ thủ nghiệp dư. Cô ấy có thể điều khiển trận đấu theo hai cách khác nhau, mô phỏng chiến lược đồng bộ và không đồng bộ.
Trong phiên bản chơi đồng bộ, cô ấy ngồi xuống với một đối thủ và chơi ván bài đó từ đầu đến cuối trước khi chuyển sang bàn tiếp theo. Mỗi nước đi của cô ấy mất 5 giây, trong khi mỗi người chơi nghiệp dư dành khoảng 55 giây để suy nghĩ. Một ván cờ điển hình có 30 lượt đổi nước (tổng cộng 60 nước đi). Điều đó có nghĩa là mỗi ván cờ kéo dài (55 + 5) × 30 = 1800 giây, khoảng 30 phút. Với 24 ván cờ, toàn bộ sự kiện kéo dài đến 12 giờ.
Trong phiên bản không đồng bộ, cô ấy đi vòng quanh phòng và thực hiện một nước đi ở mỗi bàn cờ, sau đó lập tức đi đến bàn cờ tiếp theo trong khi đối thủ hiện tại suy nghĩ về nước đi đáp trả của mình. Một vòng chơi trên 24 bàn cờ mất 24 × 5 = 120 giây, hay 2 phút. Sau 30 vòng như vậy, toàn bộ trò chơi sẽ hoàn thành trong khoảng 3600 giây, tức là 1 giờ.
Điều quan trọng cần ghi nhớ là tốc độ chơi bóng thuần túy của cô ấy không hề thay đổi; điều thay đổi là cách cô ấy tận dụng thời gian chờ đợi của đối thủ. Mã Python bất đồng bộ tuân theo nguyên tắc tương tự: nó không làm tăng tốc độ I/O, nhưng nó đảm bảo bạn đang thực hiện một việc hữu ích trong khi lẽ ra bạn sẽ phải chờ đợi mạng, ổ đĩa hoặc bất kỳ tài nguyên bên ngoài nào khác.
Yêu cầu đồng bộ so với yêu cầu bất đồng bộ: Ví dụ thực tế với API
Một trong những trường hợp sử dụng phổ biến nhất của async trong Python là xử lý các API bên ngoài, nơi mỗi yêu cầu có thể dễ dàng mất hàng trăm mili giây hoặc hơn. Để minh họa, hãy tưởng tượng bạn muốn lấy số lượng người theo dõi của một số tài khoản GitHub bằng cách sử dụng API công khai của họ.
Phương pháp đồng bộ đơn giản sẽ sử dụng một trình khách HTTP chặn phổ biến như... requests. Bạn sẽ thực hiện yêu cầu GET cho mỗi điểm cuối người dùng trong một vòng lặp, đọc tải trọng JSON, trích xuất... followers trường đó và in hoặc lưu trữ nó. Cách này đơn giản và dễ đọc, nhưng có một nhược điểm: với mỗi tài khoản bạn xử lý, chương trình thực hiện yêu cầu và sau đó chỉ chờ phản hồi trước khi bắt đầu xử lý tài khoản tiếp theo.
Vậy nếu bạn kiểm tra ba người dùng như thế này api.github.com/users/python, api.github.com/users/google và api.github.com/users/firebaseĐoạn mã sẽ gửi yêu cầu đầu tiên, chờ phản hồi từ GitHub, sau đó chuyển sang yêu cầu thứ hai, và cứ thế tiếp tục. Với số lượng người dùng ít ỏi, điều này có thể chấp nhận được, nhưng khi danh sách người dùng tăng lên hàng trăm hoặc hàng nghìn, tổng thời gian xử lý sẽ tăng vọt, bởi vì ứng dụng của bạn dành phần lớn thời gian ở trạng thái chờ, đợi máy chủ từ xa.
Để tăng tốc độ, bạn có thể chuyển sang triển khai bất đồng bộ được xây dựng trên nền tảng đó. asyncio và một máy khách HTTP có khả năng xử lý bất đồng bộ như aiohttp. Trong mô hình đó, bạn khởi chạy một số tác vụ coroutine, tất cả đều gửi yêu cầu HTTP gần như cùng một lúc. Vòng lặp sự kiện sau đó chờ phản hồi từ bất kỳ tác vụ nào trong số đó, tiếp tục mỗi tác vụ khi dữ liệu đến, thay vì chờ một yêu cầu hoàn tất hoàn toàn trước khi bắt đầu yêu cầu tiếp theo.
Khi so sánh hai phương pháp này trực tiếp, phiên bản bất đồng bộ thường vượt trội hơn hẳn, đặc biệt khi số lượng người dùng tăng lên. Thời gian xử lý mỗi yêu cầu không thay đổi, nhưng tổng thời gian để nhận được tất cả kết quả giảm mạnh vì bạn đang xử lý nhiều kết nối đồng thời thay vì tuần tự.
Các khái niệm cốt lõi: Coroutine, Vòng lặp sự kiện, Task và Future
Về cơ bản, lập trình Python bất đồng bộ hiện đại xoay quanh một vài khối xây dựng quan trọng được cung cấp chủ yếu bởi... asyncio mô-đun. Hiểu rõ những khái niệm này sẽ giúp bạn hiểu rõ hơn về toàn bộ hệ sinh thái và thiết kế các kiến trúc bất đồng bộ mạnh mẽ.
Coroutine là một loại hàm đặc biệt có thể tạm dừng và tiếp tục thực thi chính nó. Trong cú pháp hiện nay, bạn định nghĩa một cái bằng async defKhi bạn gọi nó, bạn sẽ nhận được một đối tượng coroutine cần được chờ đợi hoặc lên lịch; nó không chạy đến khi hoàn thành ngay lập tức như một hàm thông thường. Bên trong, bất cứ khi nào bạn sử dụng await Khi đối mặt với một đối tượng có thể chờ đợi (một coroutine khác, một task, một future, v.v.), Python sẽ tạm dừng coroutine đó cho đến khi thao tác được chờ đợi hoàn tất.
Vòng lặp sự kiện đóng vai trò là bộ điều phối theo dõi tất cả các coroutine, thao tác I/O và bộ hẹn giờ đang chờ xử lý, đồng thời quyết định đoạn mã nào sẽ chạy tại bất kỳ thời điểm nào. Trước đây, bạn phải lấy và quản lý vòng lặp một cách rõ ràng thông qua... asyncio.get_event_loop()nhưng trong mã Python hiện đại, mô hình được ưa chuộng là để asyncio.run() Tạo, chạy và đóng vòng lặp cho bạn xung quanh một hàm bất đồng bộ cấp cao nhất như main().
Các tác vụ là các lớp bao bọc xung quanh các coroutine, có nhiệm vụ thông báo cho vòng lặp sự kiện về việc lên lịch thực thi chúng. Bạn có thể coi chúng như những tác vụ nhẹ: vòng lặp có thể xen kẽ tiến trình giữa nhiều tác vụ mà không cần tạo ra nhiều luồng. Thông thường, bạn tạo các tác vụ với asyncio.create_task() hoặc bằng cách gọi những người trợ giúp như asyncio.gather(), hệ thống này tự quản lý một tập hợp các nhiệm vụ.
Future thể hiện các kết quả sẽ có sẵn sau này, tương tự như Promise trong JavaScript. Cả task và future đều là các đối tượng có thể chờ đợi: bạn có thể await Chúng bị tạm dừng cho đến khi hoạt động cơ bản hoàn tất. Giao thức thống nhất này giúp mã điều phối trở nên đơn giản hơn nhiều, bởi vì việc kết hợp các luồng bất đồng bộ chỉ đơn giản là chờ đợi các đối tượng phù hợp theo đúng thứ tự.
Cú pháp bất đồng bộ trong thực tế: async, await, async with và async for
async Từ khóa này không chỉ giới hạn ở định nghĩa hàm; nó còn mở rộng đến các trình quản lý ngữ cảnh và vòng lặp, cho phép các mô hình nâng cao hơn tham gia vào thế giới bất đồng bộ. Nắm vững cú pháp mở rộng này giúp bạn viết mã thanh lịch hơn liên quan đến kết nối mạng, phiên làm việc, luồng dữ liệu và các giao thức tùy chỉnh.
Hình thức phổ biến nhất là async def, định nghĩa một hàm bất đồng bộ (một nhà máy coroutine). Bên trong một chức năng như vậy, bạn sẽ sử dụng một cách tự do. await mỗi khi bạn gọi một coroutine hoặc thao tác có thể chờ đợi khác, chẳng hạn như asyncio.sleep(), một yêu cầu HTTP không đồng bộ hoặc một truy vấn cơ sở dữ liệu không đồng bộ. Lưu ý rằng bạn không thể sử dụng await trực tiếp ở cấp cao nhất của kịch bản; nó phải nằm bên trong một async def.
Mặc dù bạn có thể muốn gọi điện time.sleep() Nếu bạn sử dụng async bên trong coroutine để tạo độ trễ, điều đó sẽ hoàn toàn làm mất đi mục đích của việc sử dụng async. time.sleep() Nó chặn toàn bộ luồng, bao gồm cả vòng lặp sự kiện, do đó không có tác vụ bất đồng bộ nào khác có thể tiến hành trong thời gian đó. Thay vào đó, bạn phải sử dụng phương thức không chặn tương ứng. asyncio.sleep()Điều này giúp trả lại quyền điều khiển cho vòng lặp trong khi bộ đếm thời gian đang đếm ngược.
Python cũng hỗ trợ các trình quản lý ngữ cảnh bất đồng bộ thông qua async withđược triển khai bằng cách định nghĩa các phương pháp đặc biệt __aenter__ và __aexit__. Điều này đặc biệt hữu ích khi làm việc với các đối tượng cần một trình tự thiết lập và gỡ bỏ gọn gàng liên quan đến các thao tác bất đồng bộ, chẳng hạn như mở phiên mạng hoặc lấy tài nguyên bất đồng bộ. Một ví dụ điển hình là quản lý một aiohttp.ClientSession hoặc một yêu cầu HTTP riêng lẻ sử dụng async with các khối thay vì gọi thủ công close().
Cuối cùng, quá trình lặp bất đồng bộ được thể hiện thông qua async for, dựa vào các phương pháp ma thuật __aiter__ và __anext__ được mô tả trong PEP 492. Các trình lặp bất đồng bộ và trình tạo bất đồng bộ cho phép bạn trả về các mục theo thời gian bằng cách sử dụng await bên trong quy trình lặp, điều này rất phù hợp để truyền dữ liệu đến dần dần qua mạng hoặc từ một nguồn không đồng bộ khác.
Chạy nhiều tác vụ đồng thời với asyncio
Sức mạnh thực sự của lập trình bất đồng bộ thể hiện rõ khi bạn chạy nhiều tác vụ phụ thuộc vào I/O cùng lúc thay vì chạy từng tác vụ một. Trong hệ sinh thái lập trình bất đồng bộ của Python, các công cụ chính cho việc đó là asyncio.create_task() và asyncio.gather()Cả hai đều lên lịch thực thi coroutine trên vòng lặp sự kiện.
Với asyncio.gather()Bạn có thể khởi chạy nhiều coroutine cùng một lúc và chờ cho đến khi tất cả chúng hoàn thành, nhận kết quả dưới dạng danh sách hoặc tuple. Điều này cực kỳ phổ biến với các lô lệnh gọi HTTP, truy vấn cơ sở dữ liệu hoặc bất kỳ hoạt động bất đồng bộ lặp đi lặp lại nào. Về bản chất, gather() Nó gói mỗi coroutine vào một tác vụ và đảm bảo tất cả chúng đều được hoàn thành.
Nếu bạn quay lại ví dụ về việc lấy thông tin hồ sơ GitHub nhưng chỉnh sửa lại bằng cách sử dụng... aiohttp và asyncio.gather(), cuối cùng bạn sẽ có ba lần gọi đến một hàm như vậy fetch_user() Được ra mắt đồng thời. Mỗi tác vụ bắt đầu yêu cầu HTTP, nhường quyền điều khiển trong khi chờ dữ liệu, và sau đó tiếp tục phân tích phản hồi khi nhận được. Từ góc nhìn của người dùng, cả ba kết quả đều hiển thị gần như cùng một lúc.
Tuy nhiên, có những trường hợp bạn không muốn thực hiện hàng nghìn hoặc hàng triệu tác vụ cùng một lúc, vì điều đó có thể làm quá tải máy tính của bạn hoặc vượt quá giới hạn tốc độ xử lý bên ngoài. Một mô hình phổ biến là giới hạn khả năng xử lý đồng thời bằng cách chỉ xử lý... MAX_TASKS Thực hiện các thao tác từng bước một, bằng cách sử dụng semaphore, bounded pool hoặc logic xử lý theo lô thủ công bên trong quy trình làm việc bất đồng bộ của bạn.
Một khía cạnh quan trọng khác khi chạy nhiều tác vụ đồng thời là cách bạn xử lý lỗi; việc để một yêu cầu thất bại duy nhất làm sập toàn bộ nhóm tác vụ hiếm khi được chấp nhận trong các ứng dụng thực tế. Lý tưởng nhất là, hệ thống điều phối bất đồng bộ của bạn nên bắt và xử lý các ngoại lệ cho từng tác vụ, có thể ghi nhật ký chúng, thử lại một cách chọn lọc hoặc trả về kết quả một phần trong khi vẫn giữ nguyên phần còn lại của lô tác vụ.
Xử lý đồng thời: Lợi ích và những hạn chế
Điều quan trọng là phải phân biệt rõ ràng giữa khái niệm xử lý đồng thời và xử lý song song, bởi vì Python bất đồng bộ cung cấp khả năng xử lý đồng thời nhưng không nhất thiết cung cấp khả năng xử lý song song. Đồng thời (Concurrency) nghĩa là nhiều tác vụ đang được thực hiện trong các khoảng thời gian chồng chéo nhau, trong khi song song (Parallelism) có nghĩa là chúng thực sự chạy cùng một lúc trên nhiều lõi CPU.
Mã bất đồng bộ điển hình sử dụng asyncio Nó không tạo ra nhiều luồng hệ điều hành; thay vào đó, nó phân bổ nhiều tác vụ trong một luồng duy nhất tùy thuộc vào thời điểm mỗi tác vụ bị chặn trên I/O, tương tự như chương trình asíncrona trên Node.js. Đó là lý do tại sao nó hoạt động hiệu quả với hàng nghìn kết nối: việc chuyển đổi ngữ cảnh rất tiết kiệm chi phí vì nó mang tính hợp tác và được điều khiển bởi vòng lặp sự kiện chứ không phải hệ điều hành.
Thiết kế này đi kèm với nhiều thách thức, đặc biệt là về vấn đề phối hợp và xử lý ngoại lệ. Vì logic của bạn hiện được phân tán trên nhiều coroutine xen kẽ nhau theo thời gian, bạn cần phải cẩn trọng hơn khi chia sẻ trạng thái, truyền lỗi và dọn dẹp tài nguyên. Các lỗi như quên mất... awaitCác tác vụ không bao giờ được chờ đợi, hoặc các ngoại lệ bị bỏ qua âm thầm trong các tác vụ nền có thể rất khó phát hiện và khó gỡ lỗi.
Để duy trì tính dễ bảo trì của mã nguồn bất đồng bộ, bạn nên tuân thủ các nguyên tắc kỹ thuật vững chắc: tập trung các coroutine vào một trách nhiệm duy nhất, tập trung xử lý lỗi khi có thể và thêm nhật ký đầy đủ để hiểu những gì đang xảy ra trong quá trình thực thi. Các công cụ tốt và quy ước rõ ràng đóng vai trò rất quan trọng trong việc ngăn ngừa các vấn đề tương tự như xung đột truy cập đồng thời hoặc rò rỉ tài nguyên, ngay cả trong môi trường bất đồng bộ đơn luồng.
Khi nào mã bất đồng bộ thực sự hữu ích (và khi nào thì không)
Lập trình bất đồng bộ cực kỳ hiệu quả đối với các tác vụ phụ thuộc vào I/O, nhưng nó không phải là giải pháp thần kỳ cho mọi vấn đề về hiệu năng. Bước đầu tiên trong bất kỳ nỗ lực tối ưu hóa nào là xác định xem các điểm nghẽn của bạn đến từ I/O hay từ các phép tính phụ thuộc vào CPU.
Nếu ứng dụng của bạn dành phần lớn thời gian để chờ phản hồi mạng, đọc và ghi tệp, truy vấn cơ sở dữ liệu hoặc giao tiếp qua socket, thì lập trình bất đồng bộ gần như chắc chắn là lựa chọn phù hợp. Các ví dụ điển hình bao gồm API web giao tiếp với nhiều dịch vụ bên ngoài, các đường dẫn ETL đọc và ghi dữ liệu đồng thời từ nhiều nguồn dữ liệu, và các microservice duy trì nhiều kết nối khách hàng đồng thời.
Mặt khác, nếu khối lượng công việc của bạn chủ yếu là các thao tác CPU nặng như tính toán số liệu, xử lý hình ảnh hoặc mô phỏng phức tạp, thì chỉ riêng xử lý bất đồng bộ sẽ không giúp tăng tốc độ. Trong những trường hợp như vậy, GIL (Global Interpreter Lock) vẫn giới hạn những gì có thể chạy song song trong một tiến trình Python duy nhất. Thông thường, bạn sẽ nhận được kết quả tốt hơn với đa xử lý, các tiện ích mở rộng gốc hoặc tận dụng các backend chuyên dụng.
Trong môi trường doanh nghiệp, một chiến lược thực tế là kết hợp các kỹ thuật này: sử dụng asyncio và SDK nhận biết bất đồng bộ cho các dịch vụ đám mây (AWS, Azure và các dịch vụ khác) để giảm thiểu độ trễ và tối đa hóa thông lượng, đồng thời phân bổ các tác vụ tốn nhiều tài nguyên CPU cho các quy trình, worker hoặc dịch vụ điện toán được quản lý riêng biệt. Bằng cách đó, bạn sẽ khai thác được thế mạnh của từng công cụ thay vì phải chống lại môi trường thực thi ngôn ngữ.
Các phương pháp tốt nhất để viết mã Python bất đồng bộ
Khi bạn bắt đầu áp dụng lập trình bất đồng bộ một cách rộng rãi hơn, một số mô hình và thói quen nhất định sẽ giúp bạn tránh được những lỗi thường gặp nhất. Chúng cũng giúp mã của bạn trở nên dễ hiểu hơn đối với các đồng đội có thể chưa thực sự quen thuộc với hệ sinh thái lập trình bất đồng bộ.
Một nguyên tắc cơ bản là tránh các lệnh gọi chặn trong các luồng mã bất đồng bộ của bạn. Điều đó có nghĩa là thay thế những thứ như time.sleep() với await asyncio.sleep()và cần thận trọng với các thư viện không cung cấp API tương thích với lập trình bất đồng bộ. Nếu một gói bên thứ ba hoàn toàn đồng bộ, việc gọi nó nhiều lần từ một coroutine có thể làm tắc nghẽn vòng lặp sự kiện và phá hỏng lợi ích về tính đồng thời của bạn.
Khi bạn có một loạt các thao tác I/O độc lập, hãy ưu tiên chạy chúng đồng thời bằng cách sử dụng các tiện ích như... asyncio.gather() hoặc các nhóm tác vụ bị giới hạn bởi mức độ đồng thời tối đa. Mô hình này giúp tăng thông lượng trong khi vẫn kiểm soát được số lượng kết nối đang mở hoặc yêu cầu đang xử lý.
Theo nguyên tắc thiết kế, hãy cố gắng giữ cho coroutine tương đối nhỏ và tập trung vào một trách nhiệm rõ ràng, tương tự như cách bạn thiết kế các hàm trong mã đồng bộ sạch. Các coroutine lớn, nguyên khối, kết hợp giữa mạng lưới, logic nghiệp vụ và xử lý lỗi, nhanh chóng trở nên khó kiểm thử và khó phân tích, đặc biệt khi xảy ra lỗi giữa chừng.
Cuối cùng, hãy luôn kiểm tra xem các thành phần hệ sinh thái mà bạn dựa vào có thực sự hỗ trợ sử dụng bất đồng bộ hay không. Nhiều thư viện phổ biến cung cấp các máy khách bất đồng bộ riêng biệt hoặc các mô-đun con chuyên dụng; một số khác có thể vẫn đang chặn hoạt động ngầm ngay cả khi chúng quảng cáo các tính năng "bất đồng bộ". Đọc kỹ tài liệu và thực hiện các bài kiểm tra hiệu năng nhỏ có thể giúp bạn tránh được những sự suy giảm hiệu năng nhỏ.
Các kịch bản sử dụng thực tế và ý tưởng kiến trúc
Trong các dự án phần mềm thực tế, lập trình bất đồng bộ thể hiện ưu điểm vượt trội trong nhiều kiến trúc khác nhau, từ các hệ thống phụ trợ web truyền thống đến các hệ thống trí tuệ nhân tạo tiên tiến. Điểm chung luôn là nhu cầu xử lý nhiều thao tác I/O mà không lãng phí thời gian vào việc chờ đợi không cần thiết.
Một trường hợp điển hình là dịch vụ web cần gọi nhiều API bên ngoài để tạo ra một phản hồi duy nhất cho máy khách. Sử dụng giao thức bất đồng bộ (async), dịch vụ có thể kích hoạt tất cả các yêu cầu gửi đi cùng một lúc và tập hợp dữ liệu cuối cùng ngay khi mỗi phần đến, giúp giảm đáng kể thời gian phản hồi tổng thể. Điều này thường thấy trong kiến trúc microservice và tích hợp với các cổng thanh toán, mạng xã hội hoặc nền tảng phân tích.
Một trường hợp sử dụng quan trọng khác là kỹ thuật dữ liệu: các quy trình và tác vụ ETL thường tương tác song song với nhiều cơ sở dữ liệu, hệ thống tệp hoặc kho lưu trữ đám mây. Bằng cách đọc dữ liệu từ nhiều nguồn đồng thời và ghi kết quả ngay khi hoàn tất, bạn sẽ giảm độ trễ tổng thể và tận dụng tốt hơn băng thông khả dụng, đặc biệt khi làm việc với lưu trữ đám mây hoặc API dữ liệu dựa trên REST.
Async cũng hoạt động tốt với các bảng điều khiển và công cụ phân tích kinh doanh như Power BI, nơi các máy chủ phụ trợ phải tổng hợp dữ liệu từ các dịch vụ khác nhau mà không làm tắc nghẽn các kết nối HTTP kéo dài. Xây dựng các lớp API tùy chỉnh hoặc các dịch vụ vi mô tích hợp của bạn với asyncio có thể cải thiện khả năng phản hồi và hiệu suất khi chịu tải.
Các công ty chuyên về phần mềm tùy chỉnh, trí tuệ nhân tạo, an ninh mạng và tư vấn điện toán đám mây thường dựa nhiều vào các kỹ thuật bất đồng bộ để điều phối các quy trình làm việc gọi các mô hình AI, ghi nhật ký sự kiện, giám sát các mối đe dọa và giao tiếp với các hệ thống điều khiển đám mây. Việc kết hợp I/O bất đồng bộ để điều phối với các tiến trình xử lý riêng biệt được tối ưu hóa cho CPU để thực hiện các tác vụ nặng là một mô hình nội bộ phổ biến, tạo ra các hệ thống có khả năng mở rộng và dễ bảo trì.
Đối với nhiều nhà phát triển và nhóm, bước đầu tiên đơn giản chỉ là đưa lập trình bất đồng bộ vào những phần của ứng dụng mà rõ ràng là "phụ thuộc vào I/O", sau đó tiếp tục cải tiến khi lợi ích trở nên rõ ràng và nhóm tự tin hơn với các mô hình và công cụ.
Tóm lại, lập trình bất đồng bộ trong Python là về việc sử dụng thời gian chờ một cách khôn ngoan: bằng cách cấu trúc mã của bạn xung quanh async, awaitVới coroutines và vòng lặp sự kiện, bạn có thể xây dựng các ứng dụng hoạt động nhanh hơn, mở rộng tốt hơn khi chịu tải và tận dụng tối đa các tài nguyên sẵn có, đặc biệt là khi xử lý mạng, tập tin và các dịch vụ bên ngoài.
