8.1. Đồng thời hợp tác

Mô hình lập lịch của Asyncio là hợp tác, không phải ưu tiên. Sự phân biệt này là mô hình tư duy quan trọng nhất mà phần còn lại của mục này xây dựng dựa trên, vì vậy đáng để nắm vững trước khi có bất kỳ đoạn mã nào.

8.1.1. Lập lịch ưu tiên so với hợp tác

Bộ lập lịch ưu tiên -- loại mà hệ điều hành máy tính để bàn sử dụng để giữ nhiều chương trình chạy cùng lúc -- có thể tạm dừng bất kỳ đoạn mã nào đang chạy tại bất kỳ thời điểm nào và chuyển sang đoạn khác. Mã đang chạy không cần làm gì đặc biệt; bộ lập lịch ngắt nó lại. Điều này làm cho lập lịch ưu tiên rất linh hoạt (không có đoạn mã nào có thể làm đói các đoạn khác bằng cách chạy chậm), nhưng cũng có nghĩa là bất kỳ biến được chia sẻ nào cũng phải được bảo vệ cẩn thận, vì việc chuyển đổi có thể xảy ra ở bất kỳ đâu -- thậm chí ngay giữa lúc ghi một giá trị hoặc đọc một danh sách.

Bộ lập lịch hợp tác chỉ có thể chuyển đổi giữa các đoạn mã tại những điểm mà đoạn đang chạy chủ động trả lại quyền kiểm soát. Trong asyncio, những điểm đó là mọi await và mọi lời gọi đến coroutine có yield nội bộ (thường gặp nhất là asyncio.sleep()). Giữa hai lần await, coroutine đang chạy chiếm hoàn toàn CPU.

Hai hệ quả xuất phát từ điều đó:

  • Một coroutine không bao giờ await sẽ không bao giờ bị tạm dừng. Nếu một coroutine nằm trong vòng lặp chặt mà không có await bên trong, nó độc chiếm bộ lập lịch và không có gì khác tiến triển được. Cách khắc phục là thêm await asyncio.sleep_ms(0) (hoặc một lời gọi chờ đợi khác) tại một điểm hợp lý trong vòng lặp.

  • Trạng thái được chia sẻ an toàn giữa các lần await. Hai coroutine không thể xen kẽ nhau giữa chừng trong một thao tác không có await bên trong. Loại lỗi xảy ra khi bộ lập lịch ưu tiên dừng giữa một cập nhật nhiều bước -- một đoạn mã đọc giá trị trong khi đoạn khác đang thay đổi nó -- đơn giản là không thể xảy ra ở đây. Vẫn cần phối hợp giữa các coroutine khi nhiều coroutine phải chia sẻ tài nguyên xuyên qua các lần await, nhưng vấn đề xen kẽ giữa chừng trong một dòng lệnh không áp dụng ở đây.

8.1.2. Ba lớp

Mọi tập lệnh asyncio đều được xây dựng từ ba lớp giống nhau. Hai trang tiếp theo trình bày chi tiết về chúng; đây là các nhãn cần ghi nhớ khi đọc.

  • Coroutine -- các hàm được khai báo với async def, mỗi hàm là một đơn vị công việc độc lập có await ở những nơi thích hợp. Phần Tổng quan về Python đã giới thiệu các từ khóa async/await; trong asyncio, chúng là cách một coroutine trả quyền kiểm soát về cho bộ lập lịch.

  • Task -- một wrapper mà asyncio.create_task() đặt xung quanh một coroutine để lên lịch chạy đồng thời với coroutine hiện tại. Ứng dụng thường tạo một số task cho các công việc chạy dài (vòng lặp chụp ảnh, client mạng, bộ đọc UART, ...).

  • Vòng lặp sự kiện -- bộ máy nền theo dõi các coroutine nào đang chờ và coroutine nào sẵn sàng chạy, chuyển đổi giữa các task tại mỗi lần await. Ứng dụng không cần viết vòng lặp; nó chuyển một coroutine cấp cao nhất cho asyncio.run() và vòng lặp điều khiển mọi thứ từ đó.

Khi ứng dụng được mô tả theo cách đó -- như một tập hợp nhỏ các coroutine được kết hợp bởi một vòng lặp sự kiện -- tính đồng thời trở thành thuộc tính của hình dạng chương trình, không phải là thứ ứng dụng phải quản lý từng bước một.