Sử dụng greenlet và gevent để điều phối tác vụ bất đồng bộ trong Python

greenlet là một thư viện hỗ trợ lập trình vi luồng (microthread) thủ công, cho phép chuyển đổi bối cảnh giữa các hàm một cách tường minh. Trong khi đó, gevent xây dựng trên nền tảng greenlet nhưng tự động hoán đổi luồng khi gặp thao tác I/O — nhờ cơ chế "monkey patching" và vòng lặp sự kiện tích hợp.

Ví dụ với greenlet: Điều khiển luồng bằng tay

Greenlet không tự động hoán đổi — người lập trình phải chủ động gọi .switch() để chuyển sang greenlet khác.

Ví dụ 1: Chuyển đổi hai chiều giữa hai vi luồng

from greenlet import greenlet

def task_a(x, y):
    result = worker_b.switch(x * 2)
    print(f"task_a nhận giá trị: {result}")

def task_b(value):
    print(f"task_b xử lý: {value}")
    worker_a.switch(value + 10)

worker_a = greenlet(task_a)
worker_b = greenlet(task_b)

worker_a.switch(5, 3)  # Bắt đầu từ worker_a

Kết quả xuất ra:

task_b xử lý: 10
task_a nhận giá trị: 20

Giải thích: worker_a khởi chạy task_a(5, 3), tính x * 2 = 10, rồi chuyển sang task_b. task_b in giá trị và trả lại 20 qua worker_a.switch(...).

Ví dụ 2: Luồng tương tác tuần tự có trạng thái

from greenlet import greenlet

def fetch_data(name):
    print(f"[{name}] bắt đầu lấy dữ liệu...")
    parser.switch(name.upper())
    print(f"[{name}] tiếp tục xử lý sau phân tích")

def parse_input(label):
    print(f"[PARSER] Nhận nhãn: {label}")
    fetch_data.switch()

fetch_data = greenlet(fetch_data)
parser = greenlet(parse_input)

fetch_data.switch("user_123")

Ví dụ với gevent: Tự động hoán đổi dựa trên I/O

gevent sử dụng mô hình "cooperative multitasking": mọi hàm được chạy trong môi trường gevent sẽ tạm dừng khi gặp I/O (hoặc gọi gevent.sleep()), nhường CPU cho các tác vụ khác.

Ví dụ 1: Đồng thời thực thi hai hàm với độ trễ giả lập

import gevent

def countdown(label, n):
    for i in range(n, 0, -1):
        print(f"{label}: {i}")
        gevent.sleep(0.3)

jobs = [
    gevent.spawn(countdown, "Tác vụ A", 4),
    gevent.spawn(countdown, "Tác vụ B", 3)
]
gevent.joinall(jobs)

Kết quả (không tuần tự, xen kẽ):

Tác vụ A: 4
Tác vụ B: 3
Tác vụ A: 3
Tác vụ B: 2
Tác vụ A: 2
Tác vụ B: 1
Tác vụ A: 1

Ví dụ 2: Áp dụng monkey patch để "chuyển hóa" thư viện chuẩn

from gevent import monkey
monkey.patch_all()  # Ghi đè time.sleep(), socket, ssl, v.v.

import gevent
import time

def download_file(filename):
    print(f"Đang tải {filename}...")
    time.sleep(1.5)  # Giờ đây sẽ kích hoạt hoán đổi luồng
    print(f"Xong {filename}")

gevent.joinall([
    gevent.spawn(download_file, "report.pdf"),
    gevent.spawn(download_file, "config.json"),
    gevent.spawn(download_file, "log.tar.gz")
])

Ví dụ 3: Gửi nhiều yêu cầu HTTP song song

from gevent import monkey
monkey.patch_all()

import gevent
import requests

def fetch_url(target):
    try:
        resp = requests.get(target, timeout=5)
        print(f"{target} → {resp.status_code} ({len(resp.content)} bytes)")
    except Exception as e:
        print(f"Lỗi khi truy cập {target}: {e}")

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/status/200"
]

gevent.joinall([gevent.spawn(fetch_url, u) for u in urls])

Ví dụ 4: Giải quyết tên miền song song bằng socket của gevent

from gevent import monkey
monkey.patch_socket()  # Thay thế socket hệ thống

import gevent
from gevent import socket

def resolve_host(domain):
    try:
        ip = socket.gethostbyname(domain)
        return f"{domain} → {ip}"
    except socket.gaierror:
        return f"{domain} → lỗi DNS"

domains = ["google.com", "github.com", "python.org"]
jobs = [gevent.spawn(resolve_host, d) for d in domains]
gevent.joinall(jobs)

for job in jobs:
    print(job.value)

Ví dụ 5: Dùng hàng đợi gevent kết hợp hoán đổi chủ động

import gevent
from gevent.queue import Queue

shared_queue = Queue(maxsize=2)

def producer():
    for idx in range(6):
        shared_queue.put(f"item_{idx}")
        print(f"Đưa vào: item_{idx}")
        gevent.sleep(0.1)  # Nhường quyền ngay sau mỗi lần đưa

def consumer():
    while not shared_queue.empty():
        item = shared_queue.get()
        print(f"Tiêu thụ: {item}")
        gevent.sleep(0.15)

gevent.joinall([
    gevent.spawn(producer),
    gevent.spawn(consumer)
])

Thẻ: greenlet gevent async-python monkey-patching concurrent-programming

Đăng vào ngày 18 tháng 5 lúc 13:22