Loading Light/Dark Toggle
asyncio là gì? Vòng lặp sự kiện và I/O đồng thời trong Python

asyncio là gì?

Scrapeless Proxies định tuyến các yêu cầu từ HTTP client của riêng bạn thông qua các kết nối proxy có thể cấu hình.

asyncio là framework trong thư viện chuẩn của Python để phối hợp công việc đồng thời với các hàm bất đồng bộ và một vòng lặp sự kiện. Nó hữu ích khi một chương trình dành thời gian chờ các phản hồi mạng, tiến trình con, hoặc các thao tác khác cung cấp giao diện bất đồng bộ. Các tác vụ đang chờ có thể tạm dừng để các tác vụ đã sẵn sàng khác có thể tiếp tục.

Một job thu thập minh họa sự khác biệt. Việc lấy một trang liên quan đến chờ một máy chủ từ xa; việc phân tích một tài liệu lớn liên quan đến thực thi các lệnh cục bộ. asyncio có thể chồng lấp hoạt động thứ nhất trên nhiều yêu cầu độc lập. Nó không tự động làm cho hoạt động thứ hai chạy trên nhiều lõi CPU. Hiểu ranh giới đó giúp bạn quyết định liệu một thiết kế bất đồng bộ có cải thiện ứng dụng của bạn hay không.

asyncio thực sự quản lý những gì?

asyncio quản lý việc lập lịch và vòng đời của các thao tác bất đồng bộ, trong khi một thư viện client riêng thường triển khai giao thức ứng dụng. Giao diện I/O bất đồng bộ của Python bao gồm các tiện ích cho tác vụ, đồng bộ hoá, thao tác mạng và quản lý tiến trình con. Một HTTP client có thể dùng các tiện ích đó để chờ phản hồi mà không chiếm luồng vòng lặp sự kiện bằng một thao tác đọc chặn.

Một hàm bất đồng bộ được khai báo với async def. Gọi nó tạo ra một coroutine; bản thân lời gọi không hoàn tất phần thân hàm. Coroutine phải được await hoặc được lập lịch. Đây là lý do tại sao chỉ thêm async vào tên hoặc khai báo hàm không biến mã đồng bộ hiện có thành công việc đồng thời.

Ba khái niệm lặp lại trong các chương trình thực tế. Một coroutine mô tả một thao tác có thể tạm dừng. Một task lập lịch một coroutine và theo dõi kết quả cuối cùng của nó. Vòng lặp sự kiện phối hợp các callback và task đã sẵn sàng với trạng thái sẵn sàng I/O. Một Future biểu diễn một kết quả trong tương lai và thường xuất hiện trong các API cấp thấp; mã ứng dụng thường làm việc với coroutine và task thay vì với Future.

Vòng lặp sự kiện chia sẻ thực thi như thế nào?

Một vòng lặp sự kiện thực thi công việc theo kiểu hợp tác trong phạm vi luồng của nó. Một task đang chạy giữ quyền điều khiển cho đến khi nó hoàn thành hoặc tạm dừng tại một thao tác cần phải chờ. Khi dữ liệu mạng sẵn sàng, vòng lặp có thể tiếp tục task liên quan. Mô hình lập lịch này khác với giả định rằng mọi dòng mã bất đồng bộ đều chạy cùng một lúc.

Từ khóa await biểu thị một phụ thuộc vào một kết quả có thể await. Nó không phải là một chỉ dẫn vô điều kiện để tạm dừng. Nếu thao tác được await có thể hoàn tất ngay lập tức, việc thực thi có thể tiếp tục mà không cần tác vụ khác được chạy. Do đó một vòng lặp thực hiện nhiều tính toán đáng kể giữa các điểm tạm dừng có thể trì hoãn các thao tác không liên quan dù hàm bao quanh là bất đồng bộ.

Hãy xét một bộ thu thập catalog nhỏ. Một task chờ một trang sản phẩm, một task khác chờ một trang danh mục, và một writer chờ các bản ghi đã được kiểm tra. Mỗi task có thể tiến triển khi phụ thuộc của nó sẵn sàng. Nếu bộ phân tích sản phẩm thực hiện một phép tính đồng bộ dài trên luồng vòng lặp sự kiện, task danh mục không thể tiếp tục trong thời gian tính toán đó. Mạng có thể đã cung cấp phản hồi; ứng dụng vẫn phải đợi đến lúc xử lý nó.

Await tuần tự so với các task đồng thời

Await các thao tác độc lập lần lượt giữ chúng ở chế độ tuần tự trừ khi chúng đã được lập lịch từ trước. Nếu một hàm await việc fetch thứ nhất trước khi tạo việc fetch thứ hai, thao tác thứ hai không thể chồng lấp thao tác thứ nhất. Tạo nhiều task trước khi chờ kết quả của chúng sẽ cung cấp cho bộ lập lịch các công việc độc lập để phối hợp.

Quyền sở hữu task quan trọng không kém việc tạo task. Phải có ai đó giữ trách nhiệm quan sát kết quả, báo cáo ngoại lệ, và kết thúc công việc khi thao tác cha kết thúc. Các task nền không được theo dõi có thể khiến bên gọi nhận thông báo thành công trong khi công việc hữu ích vẫn đang chạy hoặc đã thất bại bên ngoài luồng điều khiển có thể thấy được.

Vòng đời coroutine và task của Python bao gồm TaskGroup để có quyền sở hữu có cấu trúc. Một task group chờ các task con của nó khi rời khỏi ngữ cảnh. Nếu một task con thất bại với một ngoại lệ ngoài huỷ bỏ, nhóm sẽ huỷ các task con còn lại và báo cáo lỗi thông qua cơ chế xử lý ngoại lệ đã được tài liệu hóa. Hành vi này phù hợp với một tập các thao tác mà thành công thuộc về một job cha duy nhất.

Một số ứng dụng cần các kết quả độc lập. Một báo cáo kiểm tra một số trang công khai không liên quan có thể giữ lại các quan sát thành công và ghi riêng các quan sát thất bại. Hãy quyết định chính sách đó trước khi chọn một primitive phối hợp. Nếu không, ứng dụng có thể vô tình loại bỏ các kết quả hữu ích hoặc trình bày một bộ sưu tập chưa đầy đủ như thể đã đầy đủ.

Tại sao đồng thời cần giới hạn dung lượng

Giới hạn đồng thời giữ ứng dụng trong giới hạn dung lượng của client, đích đến, và bộ nhớ lưu trữ. Tạo một task cho mọi URL được phát hiện có thể tiêu tốn bộ nhớ trước khi phản hồi đầu tiên tới. Ngay cả khi socket bị giới hạn, các đối tượng task trong hàng đợi, bộ đệm phản hồi, và các bản ghi đang chờ có thể tiếp tục tăng lên.

Một semaphore có thể giới hạn số thao tác bên trong một vùng được bảo vệ. Phạm vi của nó nên bao phủ tài nguyên mà bạn thực sự muốn giới hạn. Nếu nó chỉ bao phủ việc thiết lập kết nối nhưng giải phóng trước khi phản hồi được tiêu thụ, các phần thân phản hồi lớn vẫn có thể tích luỹ không giới hạn. Hãy giữ toàn bộ vòng đời yêu cầu trong giới hạn khi đó là đơn vị dự định.

Một hàng đợi có giới hạn bổ sung backpressure giữa các giai đoạn. Bên sản xuất sẽ chờ khi hàng đợi đầy thay vì tiếp tục phát hiện công việc vô thời hạn. hợp đồng hàng đợi asyncio phân biệt việc truy xuất một mục với việc đánh dấu hoàn tất xử lý của nó. Điều này hữu ích khi một worker phải kiểm tra và lưu một bản ghi trước khi công việc của producer được xem là hoàn thành.

Nơi Mã Chặn (Blocking Code) Gây Ra Vấn Đề

Mã chặn ngăn luồng event-loop phục vụ các tác vụ khác trong khi thao tác chặn đang chạy. Các ví dụ thường gặp là các lời gọi HTTP đồng bộ, thao tác tệp chậm, và các quy trình phân tích (parsing) hoặc chuyển đổi kéo dài. Một lớp bọc bất đồng bộ quanh lời gọi như vậy không làm thay đổi hành vi của lời gọi đó.

Hãy dùng client bất đồng bộ nguyên bản khi thư viện hỗ trợ. Với một hàm I/O chặn đã có sẵn, asyncio.to_thread có thể chuyển hàm đó sang một luồng khác để luồng event-loop vẫn rảnh. Đây là một cầu nối cho một thao tác cụ thể, không phải bảo chứng rằng toàn bộ chương trình đã trở thành bất đồng bộ hoặc an toàn luồng.

Công việc nặng CPU xứng đáng có một quyết định riêng. Một worker dùng tiến trình có thể phù hợp cho tính toán lớn, tùy theo runtime Python và các thư viện liên quan. Hãy tính đến chi phí di chuyển dữ liệu vào và ra khỏi worker đó. Gửi một đối tượng đã phân tích khổng lồ qua ranh giới tiến trình có thể xóa sạch lợi ích của việc phân tán phép tính.

Tài liệu hướng dẫn phát triển asyncio mô tả các công cụ debug và các vấn đề liên quan đến mã chặn. Trong quá trình điều tra, hãy ghi nhận riêng thời lượng tác vụ và khả năng phản hồi của event-loop so với thời gian phản hồi từ xa. Một thao tác đầu-cuối chậm có thể đang phải chờ ứng dụng chứ không phải website.

Hủy Bỏ, Thời Hạn và Dọn Dẹp Tài Nguyên

Hủy bỏ yêu cầu công việc bất đồng bộ dừng lại tại một thời điểm có thể hủy; nó không chứng minh rằng thao tác từ xa chưa bao giờ được nhận. Nếu một client gửi một lệnh ghi rồi hủy trong khi đang chờ, máy chủ có thể đã áp dụng lệnh ghi đó. Quy trình phải thể hiện được sự bất định này thay vì coi hủy bỏ như một thao tác hoàn tác (rollback).

Hãy dùng các đường dọn dẹp (cleanup path) cho tài nguyên mà một task sở hữu. Một context manager bất đồng bộ có thể đóng một client hoặc luồng phản hồi khi phạm vi của nó kết thúc. Một khối finally có thể giải phóng tài nguyên ứng dụng ngay cả khi một task thoát ra do bị hủy. Tránh nuốt mất việc hủy bỏ nếu không có một chính sách sở hữu rõ ràng, vì quản lý task có cấu trúc phụ thuộc vào việc hủy được truyền đi đúng cách.

Timeout mạng và thời hạn (deadline) của quy trình trả lời những câu hỏi khác nhau. Timeout phía client có thể giới hạn thời gian chờ cho một pha mạng. Thời hạn quy trình có thể giới hạn vòng đời của một thao tác lớn hơn, bao gồm xếp hàng và xử lý. Hãy theo dõi nơi thời hạn bị đạt tới để người vận hành có thể phân biệt một hàng đợi cục bộ quá tải với một điểm đến chậm.

Cách asyncio Phù Hợp Với Pipeline Dữ Liệu Web

asyncio phù hợp với lớp điều phối của một pipeline mà các phụ thuộc của nó cung cấp các thao tác bất đồng bộ. Một bố cục hữu ích là tách riêng khám phá, lấy dữ liệu, kiểm tra, và lưu trữ. Mỗi giai đoạn nhận một đầu vào rõ ràng và tạo ra một kết quả có đủ danh tính để nối nó với mục ban đầu.

Với một ví dụ kiểm tra danh mục giả định, hãy giữ lại URL được yêu cầu, URL cuối cùng, thời điểm quan sát, trạng thái phản hồi, và kết quả phân tích. Một trang trả về HTML nhưng không có các trường sản phẩm mong đợi nên được ghi nhận là một quan sát không hợp lệ. Việc lập lịch nhanh hơn không làm phản hồi đó trở nên hữu ích. Một phép trích xuất rỗng và một tập hợp rỗng hợp lệ là hai kết quả khác nhau.

Tái sử dụng HTTP client trong vòng đời dự kiến của nó và giữ trạng thái phiên một cách có chủ ý. Phần thảo luận về cấu hình proxy HTTPX cung cấp bối cảnh client liên quan, trong khi mô hình kết nối proxy Scrapeless hiện tại mô tả lớp mạng. Định tuyến proxy và lập lịch task vẫn là các điều khiển tách biệt; thêm một proxy không giới hạn độ đồng thời của ứng dụng của bạn.

Khi chọn Scrapeless Proxies, hãy đánh giá địa lý cần thiết và tính liên tục kết nối song song với thiết kế client. Tham khảo Scrapeless pricing cho sản phẩm đã chọn trước khi ước tính một lô. Hãy dựa ước tính trên khối lượng công việc request và response thực tế, bao gồm cả nội dung không vượt qua kiểm tra hợp lệ.

Khi Nào Thiết Kế Đồng Bộ Tốt Hơn?

Một thiết kế đồng bộ thường dễ bảo trì hơn khi công việc có rất ít I/O độc lập để chồng lấp. Một lệnh ngắn chỉ lấy một tài liệu rồi phân tích ngay có thể được lợi rất ít từ điều phối bằng event-loop. Các phụ thuộc đồng bộ hiện có cũng có thể khiến một chuyển đổi bán phần trở nên phức tạp hơn so với mã gốc.

Hãy chọn asyncio khi bạn có thể nhận ra những khoảng thời gian chờ có ý nghĩa, tách riêng công việc độc lập, và quản lý vòng đời của nó. Hãy chọn một thiết kế đơn giản hơn khi các thao tác vốn dĩ là tuần tự hoặc khi tính toán chiếm ưu thế trong thời gian chạy. Quyết định nên theo hồ sơ công việc, thay vì giả định chung rằng mã bất đồng bộ luôn nhanh hơn.

Kết Luận

asyncio điều phối công việc đang chờ thông qua việc tạm dừng rõ ràng và lập lịch task. Bắt đầu với một job thu thập có giới hạn, gán chủ sở hữu cho mỗi task, và tách biệt chờ mạng với xử lý cục bộ. Xem lại hành vi tắt (shutdown) và kiểm tra kết quả trước khi tăng tải công việc; những lựa chọn đó quyết định việc độ đồng thời có tạo ra dữ liệu đáng tin hay không.

Kết Nối Bộ Thu Thập Đồng Thời Của Bạn

Ghép một quy trình thu thập Python có giới hạn với tuyến proxy và chính sách phiên mà nó cần.

Đăng ký hôm nay và nhận $5 tín dụng miễn phí — không cần thẻ tín dụng.

Nhận $5 Tín Dụng Của Bạn →

FAQ

asyncio có phải là một phần của Python không?

asyncio là một phần của thư viện chuẩn Python. Các client HTTP hoặc cơ sở dữ liệu của bên thứ ba có thể xây dựng trên nó, nhưng chúng là các gói riêng với hành vi kết nối và cấu hình riêng.

asyncio có chạy mã Python trên nhiều lõi CPU không?

Chỉ riêng việc lập lịch của asyncio không phân phối việc thực thi Python trên nhiều lõi CPU. Một event loop chạy các task trong luồng của nó, trong khi các executor hoặc tiến trình riêng có thể được dùng cho công việc cần một ngữ cảnh thực thi khác.

Mỗi await có phải đều chuyển sang một task khác không?

Biểu thức await không phải lúc nào cũng chuyển sang tác vụ khác. Một awaitable hoàn thành ngay lập tức có thể tiếp tục trên luồng thực thi hiện tại mà không bị tạm dừng.

asyncio có thể kiểm soát việc xoay vòng proxy không?

asyncio có thể lập lịch các thao tác sử dụng proxy, nhưng client và nhà cung cấp proxy quyết định hành vi định tuyến và xoay vòng. Hãy giữ chính sách phiên tách biệt với cơ chế kiểm soát song song của ứng dụng.

Tại sao một ứng dụng async vẫn trở nên không phản hồi?

Một ứng dụng async có thể trở nên không phản hồi khi các lời gọi chặn hoặc các phép tính dài chiếm dụng luồng vòng lặp sự kiện của nó. Hãy xem xét xử lý cục bộ, mức tăng của hàng đợi và giới hạn kết nối trước khi quy mọi độ trễ cho dịch vụ từ xa.

Tài liệu tham khảo