Mô-đun `concurrent` trong Python
Trong bài viết này, chúng tôi sẽ giải thích về mô-đun concurrent trong Python.
Trong khi làm rõ các khái niệm về song song và đồng thời, chúng tôi sẽ giải thích cách triển khai xử lý bất đồng bộ bằng mô-đun concurrent với các ví dụ thực tế.
YouTube Video
Mô-đun concurrent trong Python
Khi tăng tốc xử lý trong Python, việc chú ý đến sự khác biệt giữa xử lý đồng thời và song song là rất quan trọng. Mô-đun concurrent là một phương tiện quan trọng để xử lý bất đồng bộ một cách an toàn và đơn giản, trong khi vẫn lưu ý các sự khác biệt này.
Sự khác biệt giữa đồng thời và song song
-
Đồng thời nghĩa là thiết kế quy trình sao cho nhiều tác vụ tiến hành bằng cách chuyển đổi giữa chúng theo các đơn vị công việc nhỏ. Ngay cả khi các tác vụ không thực sự chạy cùng lúc, việc tận dụng "thời gian chờ" cho phép bạn làm cho toàn bộ quá trình hiệu quả hơn.
-
Song song là một cơ chế thực thi đồng thời nhiều tác vụ về mặt vật lý. Bằng cách sử dụng nhiều lõi CPU, việc xử lý được tiến hành đồng thời.
Cả hai đều là kỹ thuật tăng tốc xử lý, nhưng đồng thời là vấn đề thiết kế 'làm thế nào để tiến hành', còn song song là vấn đề thực thi 'chạy như thế nào', khiến chúng hoàn toàn khác nhau về bản chất.
Mô-đun concurrent là gì?
concurrent là một thư viện tiêu chuẩn trong Python cung cấp API cấp cao để xử lý đồng thời và song song một cách an toàn, đơn giản. Nó được thiết kế để bạn có thể tập trung vào 'thực thi các nhiệm vụ' mà không cần phải lo lắng về các thao tác cấp thấp như tạo và quản lý luồng hoặc tiến trình.
Vai trò của ThreadPoolExecutor và ProcessPoolExecutor
Mô-đun concurrent cung cấp hai tùy chọn chính tùy thuộc vào tính chất của bài toán.
-
ThreadPoolExecutorĐiều này phù hợp với việc triển khai đồng thời, đặc biệt là các tác vụ có nhiều thời gian chờ I/O như thao tác mạng hoặc tệp tin. Bằng cách chuyển đổi giữa các nhiệm vụ, nó tận dụng hiệu quả thời gian chờ. -
ProcessPoolExecutorViệc triển khai này hướng đến xử lý song song và thích hợp với các nhiệm vụ sử dụng nhiều CPU. Nó sử dụng nhiều tiến trình để tận dụng tối đa các lõi CPU sẵn có một cách song song.
Do đó, một tính năng chính của mô-đun concurrent là cung cấp một cấu trúc giúp bạn lựa chọn phù hợp giữa đồng thời và song song khi cần thiết.
Cơ bản về ThreadPoolExecutor (Dành cho các tác vụ I/O)
ThreadPoolExecutor phù hợp với các nhiệm vụ bị giới hạn bởi I/O như giao tiếp mạng hoặc thao tác tệp. Nó phân phối các nhiệm vụ cho nhiều luồng, tận dụng hiệu quả thời gian chờ.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in futures:
13 print(future.result())- Trong ví dụ này, nhiều tác vụ I/O với thời gian chờ một giây được thực thi đồng thời. Bằng cách sử dụng
submit, các hàm được đăng ký như các tác vụ bất đồng bộ, và bằng cách gọiresult(), bạn có thể chờ hoàn thành và lấy kết quả, giúp bạn triển khai xử lý đồng thời tận dụng hiệu quả thời gian chờ một cách ngắn gọn.
Đồng thời đơn giản với map
Nếu không cần kiểm soát phức tạp, việc sử dụng map sẽ giúp mã nguồn gọn gàng hơn.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = executor.map(fetch_data, range(5))
11
12 for result in results:
13 print(result)- Trong ví dụ này, nhiều tác vụ I/O được thực thi đồng thời bằng
ThreadPoolExecutor.map. Vìmaptrả về kết quả theo đúng thứ tự đầu vào, bạn có thể viết mã gần giống như xử lý tuần tự, và thực thi đồng thời mà không cần quan tâm đến bất đồng bộ—đây là một lợi thế lớn.
Cơ bản về ProcessPoolExecutor (Dành cho các nhiệm vụ sử dụng nhiều CPU)
Đối với các tác vụ tính toán nặng tận dụng tối đa CPU, bạn nên dùng tiến trình thay vì luồng. Điều này giúp bạn tránh được giới hạn của GIL (Global Interpreter Lock).
1from concurrent.futures import ProcessPoolExecutor
2
3def heavy_calculation(n):
4 # Simulate a CPU-bound task
5 total = 0
6 for i in range(10_000_000):
7 total += i * n
8 return total
9
10if __name__ == "__main__":
11 with ProcessPoolExecutor(max_workers=4) as executor:
12 results = executor.map(heavy_calculation, range(4))
13
14 for result in results:
15 print(result)Trong ví dụ này, các phép tính sử dụng nhiều CPU được thực thi song song bằng ProcessPoolExecutor. Do việc tạo tiến trình liên quan, cần có phần kiểm tra __main__, giúp xử lý song song an toàn tận dụng nhiều lõi CPU.
Xử lý theo thứ tự hoàn thành với as_completed
as_completed hữu ích khi bạn muốn xử lý kết quả theo thứ tự hoàn thành.
1from concurrent.futures import ThreadPoolExecutor, as_completed
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in as_completed(futures):
13 print(future.result())- Trong ví dụ này, nhiều tác vụ bất đồng bộ được thực thi song song và kết quả được truy xuất theo thứ tự hoàn thành. Sử dụng
as_completedgiúp xử lý kết quả nhanh bất kể thứ tự nhiệm vụ, phù hợp với việc hiển thị tiến độ hoặc các tình huống cần xử lý theo thứ tự hoàn thành.
Xử lý ngoại lệ
Trong concurrent, ngoại lệ sẽ được ném ra khi bạn gọi result().
1from concurrent.futures import ThreadPoolExecutor
2
3def risky_task(n):
4 # Simulate a task that may fail for a specific input
5 if n == 3:
6 raise ValueError("Something went wrong")
7 return n * 2
8
9with ThreadPoolExecutor() as executor:
10 futures = [executor.submit(risky_task, i) for i in range(5)]
11
12 for future in futures:
13 try:
14 print(future.result())
15 except Exception as e:
16 print("Error:", e)- Ví dụ này cho thấy ngay cả khi một vài tác vụ ném ra ngoại lệ, các tác vụ khác vẫn tiếp tục chạy và bạn có thể xử lý ngoại lệ riêng biệt khi truy xuất kết quả. Bằng cách sử dụng
Futurecủaconcurrent, quan trọng là các trường hợp thành công và thất bại trong xử lý bất đồng bộ đều có thể xử lý an toàn.
Hướng dẫn chọn giữa luồng (threads) và tiến trình (processes)
Để sử dụng hiệu quả đồng thời và song song, bạn cần chọn phương án phù hợp tùy vào tính chất nhiệm vụ.
Trong thực tế, các tiêu chí sau có thể giúp bạn quyết định.
- Đối với các tiến trình có nhiều thời gian chờ I/O, chẳng hạn như giao tiếp hoặc thao tác tệp, hãy sử dụng
ThreadPoolExecutor. - Đối với nhiệm vụ nặng sử dụng CPU, sử dụng
ProcessPoolExecutor. - Nếu có nhiều nhiệm vụ đơn giản, sử dụng
mapgiúp mã ngắn gọn hơn. - Nếu cần kiểm soát chính xác thứ tự thực thi hoặc xử lý ngoại lệ, hãy kết hợp
submitvàas_completed.
Lợi ích của việc sử dụng concurrent
Với mô-đun concurrent, bạn có thể xử lý bất đồng bộ an toàn và trực quan.
Các lợi ích chính như sau:.
- Bạn không cần quá bận tâm đến việc quản lý luồng hay tiến trình cấp thấp.
- Nó là một phần của thư viện tiêu chuẩn Python, bạn có thể sử dụng một cách yên tâm.
- Mã nguồn trở nên dễ đọc và dễ bảo trì hơn.
- Nó rất phù hợp để bắt đầu tìm hiểu về đồng thời và song song.
Chỉ cần ghi nhớ các hướng dẫn này cũng sẽ giảm thiểu rất nhiều rủi ro khi ứng dụng concurrent.
Tóm tắt
Mô-đun concurrent là tùy chọn tiêu chuẩn cho đồng thời và song song thực tế trong Python. Nó giúp cải thiện hiệu suất mà không cần thay đổi nhiều nội dung xử lý, đây là một lợi ích lớn trong thực tế. Nhờ sử dụng concurrent, bạn có thể triển khai xử lý bất đồng bộ ngắn gọn trong khi vẫn quản lý an toàn ngoại lệ và kiểm soát thực thi.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.