Python có hai cách chạy song song, và chọn sai thì chương trình không những không nhanh hơn mà còn chậm đi. Điều quyết định nằm ở một thứ tên là GIL.
GIL là gì và vì sao nó quan trọng
GIL (Global Interpreter Lock) là khóa cho phép chỉ một luồng thực thi bytecode Python tại một thời điểm. Nghĩa là nhiều luồng không giúp bạn tính toán nhanh hơn. Nhưng khi một luồng đang chờ mạng hoặc đọc file, nó nhả GIL cho luồng khác chạy — và đó là chỗ threading tỏa sáng.
| Loại công việc | Nên dùng |
|---|---|
| Tải web, gọi API, đọc/ghi file, truy vấn DB | threading hoặc asyncio |
| Xử lý ảnh, nén, tính toán số học nặng | multiprocessing |
Threading cho tác vụ I/O
from concurrent.futures import ThreadPoolExecutor
import requests
urls = ["https://example.com"] * 20
def tai(url):
return requests.get(url, timeout=10).status_code
with ThreadPoolExecutor(max_workers=10) as pool:
for ma in pool.map(tai, urls):
print(ma)
Dùng ThreadPoolExecutor thay vì tự tạo Thread — nó quản lý vòng đời luồng và thu kết quả giúp bạn.
Multiprocessing cho tác vụ CPU
from concurrent.futures import ProcessPoolExecutor
def nang(n):
return sum(i * i for i in range(n))
if __name__ == "__main__": # bắt buộc trên Windows và macOS
with ProcessPoolExecutor() as pool:
print(list(pool.map(nang, [10**6] * 8)))
Mỗi tiến trình có GIL riêng nên chạy thật sự song song trên nhiều nhân CPU. Đổi lại, dữ liệu phải được đóng gói (pickle) để truyền qua lại — nên chỉ đáng dùng khi công việc đủ nặng.
Khóa khi nhiều luồng ghi chung
import threading
khoa = threading.Lock()
tong = 0
def cong(n):
global tong
with khoa: # tránh race condition
tong += n
Chọn thế nào cho nhanh
- Đo trước bằng
time.perf_counter()— đừng đoán. - Chờ mạng nhiều? Thử
asynciotrước, rồi mới tới threading. - CPU chạy 100% một nhân? Dùng multiprocessing.
- Vẫn chậm? Cân nhắc NumPy hoặc viết lại phần nóng bằng thư viện C.
Song song hóa luôn thêm độ phức tạp. Chỉ làm khi bạn đã đo được rằng nó thật sự đáng.
