Concurrency và parallelism là hai kỹ thuật cho phép bạn chạy đồng thời một số chương trình. Python có nhiều lựa chọn để xử lý cùng lúc và song song hàng loạt tác vụ nhưng điều đó lại có thể khiến nhiều người bối rối.
Vì thế, hãy cùng nhau khám phá các công cụ và thư viện sẵn có để triển khai đúng cách đồng thời và song song trong Python nhé!
Concurrency và Parallelism là gì?
Concurrency và parallelism chỉ hai nguyên tắc cơ bản của việc triển khai nhiệm vụ trong điện toán. Mỗi nguyên tắc lại có đặc điểm riêng.
1. Concurrency là tính năng của một chương trình để quản lý nhiều tác vụ cùng lúc mà không cần thực thi chúng ở cùng thời điểm chính xác. Nó xoay quanh ý tưởng xen kẽ các nhiệm vụ, chuyển đổi giữa chúng theo phương pháp hiện đồng thời.
2. Parallelism liên quan tới việc triển khai hàng loạt nhiệm vụ song song. Nó thường tận dụng nhiều lõi hay bộ vi xử lý CPU. Parallelism đạt được triển khai đồng thời thực sự, cho phép bạn thực hiện các nhiệm vụ nhanh hơn và phù hợp cho các hoạt động tính toán mở rộng.
Tầm quan trọng của Concurrency và Parallelism
- Sử dụng tài nguyên: Concurrency cho phép sử dụng hiệu quả tài nguyên hệ thống, đảm bảo các nhiệm vụ đó đang tích cực tiến triển thay vì chờ đợi tài nguyên bên ngoài.
- Phản hồi: Concurrency có thể cải thiện khả năng phản hồi của ứng dụng, nhất là trong bối cảnh liên quan tới giao diện người dùng hoặc web server.
- Hiệu suất: Parallelism quan trọng trong việc đạt hiệu suất tối ưu, nhất là với những nhiệm vụ liên quan tới CPU như tính toán phức tạp, xử lý dữ liệu và mô phỏng.
- Có thể mở rộng: Cả đồng thời và song song đều cần cho xây dựng các hệ thống có thể mở rộng.
- Kiểm chứng trong tương lai: Khi xu hướng phần cứng liên tục ủng hộ các bộ vi xử lý đa lõi, khả năng khai thác tính song song sẽ ngày càng trở nên cần thiết.
Concurrency trong Python
Bạn có thể đạt được sự đồng thời trong Python bằng lập trình luồng và không đồng bộ với thư viện asyncio.
Threading (luồng) trong Python
Threading là cơ chế đồng thời trong Python, cho phép bạn tạo và quản lý các nhiệm vụ trong một quá trình đơn giản. Thread phù hợp với những kiểu nhiệm vụ cụ thể, đặc biệt là tác vụ giới hạn I/O và có thể tận hưởng lợi ích từ việc thực thi đồng thời.
Mô đun threading của Python cung cấp giao diện cấp cao để tạo vào quản lý luồng. Trong khi GIL (Global Interpreter Lock) hạn chế các luồng về mặt parallelism thực sự, chúng vẫn có thể đạt được tính đồng thời bằng cách xen kẽ hiệu quả các nhiệm vụ.
Code bên dưới hiện một ví dụ triển khai tính đồng thời bằng thread. Nó dùng thư viện truy vấn Python để gửi một truy vấn HTTP, một nhiệm vụ khối I/O phổ biến. Nó cũng dùng mô đun thời gian để tính thời gian thực thi.
import requests
import time
import threading
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# hàm truy vấn một URL
def download_url(url):
response = requests.get(url)
print(f"Downloaded {url} - Status Code: {response.status_code}")
# Thực thi không có luồng và đo thời gian thực hiện
start_time = time.time()
for url in urls:
download_url(url)
end_time = time.time()
print(f"Sequential download took {end_time - start_time:.2f} seconds\n")
# Thực thi với luồng, reset thời gian đo thời điểm triển khai mới
start_time = time.time()
threads = []
for url in urls:
thread = threading.Thread(target=download_url, args=(url,))
thread.start()
threads.append(thread)
# Đợi tất cả phân luồng hoàn thành
for thread in threads:
thread.join()
end_time = time.time()
print(f"Threaded download took {end_time - start_time:.2f} seconds")
Chạy chương trình này, bạn sẽ thấy các truy vấn được phân luồng chạy nhanh hơn truy vấn theo thứ tự. Dù sự khác biệt chỉ là một phần của giây nhưng bạn sẽ cảm nhận rõ ràng hơn hiệu suất được cải thiện khi dùng các thread cho nhiệm vụ liên quan tới I/O.
Lập trình bất đồng bộ với Asyncio
Asyncio cung cấp một lặp sự kiện, quản lý các nhiệm vụ bất đồng bộ tên coroutine. Chúng là các hàm mà bạn có thể tạm dừng và tiếp tục, khiến chúng trở nên lý tưởng cho những nhiệm vụ liên quan tới I/O. Thư viện này đặc biệt hữu ích cho các trường hợp mà nhiệm vụ liên quan tới việc đợi tài nguyên bên ngoài, chẳng hạn như truy vấn mạng.
Bạn có thể chỉnh sửa ví dụ gửi truy vấn trước đó để làm việc với asyncio:
import asyncio
import aiohttp
import time
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# hàm bất đồng bộ để truy vấn URL
async def download_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
content = await response.text()
print(f"Downloaded {url} - Status Code: {response.status}")
# Hàm bất đồng bộ chính
async def main():
# Tạo danh sách nhiệm vụ download đồng thời từng URL
tasks = [download_url(url) for url in urls]
# Thu thập và triển khai đồng thời các nhiệm vụ
await asyncio.gather(*tasks)
start_time = time.time()
# Chạy hàm bất đồng bộ chính
asyncio.run(main())
end_time = time.time()
print(f"Asyncio download took {end_time - start_time:.2f} seconds")
Dùng code này, bạn có thể tải đồng thời các trang web bằng asyncio và tận dụng hoạt động I/O không đồng bộ. Điều này có thể hiệu quả hơn việc phân luồng cho những nhiệm vụ liên quan tới I/O.
Parallelism trong Python
Bạn có thể triển khai song song bằng mô đun multiprocessing của Python, cho phép bạn tận dụng đầy đủ bộ vi xử lý đa lõi.
Xử lý đa lõi trong Python
Mô đun multiprocessing của Python cung cấp cách đạt được sự song song bằng việc tạo những quá trình tách biệt với trình phiên dịch Python và không gian bộ nhớ riêng. Điều này vượt qua hiệu quả Global Interpreter Lock (GIL), khiến nó phù hợp với các nhiệm vụ liên quan tới CPU.
import requests
import multiprocessing
import time
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# hàm truy vấn một URL
def download_url(url):
response = requests.get(url)
print(f"Downloaded {url} - Status Code: {response.status_code}")
def main():
# Tạo một pool multiprocessing với một số lượng quá trình được chỉ định
num_processes = len(urls)
pool = multiprocessing.Pool(processes=num_processes)
start_time = time.time()
pool.map(download_url, urls)
end_time = time.time()
# Đóng pool và đợi cho toàn bộ quá trình hoàn tất
pool.close()
pool.join()
print(f"Multiprocessing download took {end_time-start_time:.2f} seconds")
main()
Ở ví dụ này, multiprocessing sinh ra nhiều quy trình, cho phép hàm download_url chạy song song.
Khi nào dùng concurrency và parallelism?
Lựa chọn giữa concurrency và parallelism phụ thuộc vào bản chất của nhiệm vụ và tính sẵn có của tài nguyên hardware.
Bạn có thể dùng concurrency khi xử lý những nhiệm vụ liên quan tới I/O, như đọc và ghi file hoặc tạo các truy vấn mạng, và khi lo ngại về hạn chế bộ nhớ.
Dùng multiprocessing khi bạn có các nhiệm vụ liên quan tới CPU mà có thể tận hưởng lợi ích từ parallelism thực sự và khi bạn có sự cô lập mạnh mẽ giữa các nhiệm vụ, nơi mà thất bại của một nhiệm vụ sẽ không ảnh hưởng tới các nhiệm vụ khác.
Trên đây là những điều bạn cần biết về parallelism và concurrency trong Python. Hi vọng bài viết hữu ích với các bạn.