Trong hướng dẫn này, bạn sẽ khám phá ra anti-pattern khi sử dụng ThreadPool và cách tránh nó trong các dự án của bạn.
Bắt đầu nào
Mục lục
- ThreadPool có thể chậm hơn vòng lặp For
- ThreadPool có thể chậm hơn đối với các tác vụ liên quan đến CPU
- Ví dụ về ThreadPool chậm hơn vòng lặp For
- Nhiệm vụ giới hạn CPU trong vòng lặp For
- Nhiệm vụ giới hạn CPU trong ThreadPool
- Tại sao The ThreadPool không chậm hơn nhiều?
- Câu hỏi thường gặp
- Điều gì xảy ra nếu chúng ta sử dụng nhiều chủ đề hơn trong ThreadPool?
- Điều gì xảy ra nếu chúng ta sử dụng một luồng trong ThreadPool?
- Worker Threads trong ThreadPool có chạy trên các lõi khác nhau không?
- Việc sử dụng nhóm đa xử lý sẽ tăng tốc ví dụ này chứ?
- mang đi
ThreadPool có thể chậm hơn vòng lặp For
đa xử lý. hồ bơi. ThreadPool trong Python cung cấp một nhóm các luồng có thể tái sử dụng để thực hiện các tác vụ đặc biệt
Một đối tượng nhóm luồng kiểm soát nhóm luồng công nhân mà công việc có thể được gửi tới
— đa xử lý — Song song dựa trên quy trình
Lớp ThreadPool mở rộng lớp Pool. Lớp Pool cung cấp một nhóm các quy trình công nhân cho đồng thời dựa trên quy trình
Mặc dù lớp ThreadPool nằm trong mô-đun đa xử lý nhưng lớp này cung cấp tính đồng thời dựa trên luồng và phù hợp nhất với các tác vụ liên kết với IO, chẳng hạn như đọc hoặc ghi từ ổ cắm hoặc tệp
Một ThreadPool có thể được cấu hình khi nó được tạo, nó sẽ chuẩn bị các luồng mới
Chúng ta có thể đưa ra các tác vụ một lần cho ThreadPool bằng các phương thức như apply[] hoặc chúng ta có thể áp dụng chức năng tương tự cho một mục có thể lặp lại bằng các phương thức như map[]
Sau đó, kết quả cho các tác vụ đã phát hành có thể được truy xuất đồng bộ hoặc chúng tôi có thể truy xuất kết quả của các tác vụ sau này bằng cách sử dụng các phiên bản không đồng bộ của các phương thức, chẳng hạn như apply_async[] và map_async[]
ThreadPool được thiết kế để tăng tốc chương trình của bạn bằng cách thực hiện đồng thời các tác vụ
Tuy nhiên, trong một số trường hợp sử dụng, việc sử dụng ThreadPool có thể khiến chương trình của bạn chậm hơn. Đôi khi chậm hơn đáng kể so với việc thực hiện cùng một tác vụ trong vòng lặp for
Làm thế nào ThreadPool có thể làm cho chương trình của bạn chậm hơn?
Chạy các vòng lặp của bạn bằng cách sử dụng tất cả các CPU, tải xuống cuốn sách MIỄN PHÍ của tôi để tìm hiểu cách thực hiện
ThreadPool có thể chậm hơn đối với các tác vụ liên quan đến CPU
Sử dụng ThreadPool cho tác vụ liên quan đến CPU có thể chậm hơn so với việc không sử dụng nó
Điều này là do các chuỗi Python bị hạn chế bởi Khóa phiên dịch toàn cầu hoặc GIL
GIL là một mẫu lập trình trong trình thông dịch Python tham chiếu [CPython] sử dụng đồng bộ hóa để đảm bảo rằng chỉ một luồng có thể thực thi các lệnh tại một thời điểm trong quy trình Python
Điều này có nghĩa là mặc dù chúng ta có thể có nhiều luồng trong nhóm luồng nhưng chỉ một luồng có thể thực thi tại một thời điểm
Điều này tốt khi các tác vụ được thực thi bởi nhóm luồng đang bị chặn, chẳng hạn như các tác vụ liên kết với IO có thể đọc từ một tệp hoặc kết nối internet
Đây là sự cố khi các tác vụ được thực thi bởi nhóm luồng bị ràng buộc bởi CPU, nghĩa là tốc độ thực thi của chúng được xác định bởi tốc độ của CPU. Các tác vụ này không bị chặn và do đó chạy nhanh nhất có thể. Do có GIL, các luồng thực thi các tác vụ này sẽ chạy lần lượt và dẫm lên nhau thông qua chuyển đổi ngữ cảnh
Chuyển đổi ngữ cảnh là một mẫu lập trình cho phép nhiều hơn một luồng thực thi chạy trên một CPU, chẳng hạn như. g. thay đổi “bối cảnh” cho CPU thực thi các lệnh. Trong một context switch, hệ điều hành sẽ lưu trữ trạng thái của luồng đang thực thi để nó có thể được tiếp tục lại sau đó và cho phép một luồng thực thi khác chạy và lưu trữ trạng thái của nó
Đây là một vấn đề với các tác vụ liên quan đến CPU vì chuyển ngữ cảnh là một hoạt động tương đối tốn kém. Có nhiều luồng chạy cùng lúc với cùng mức độ ưu tiên trên cùng một loại tác vụ sẽ có thể buộc hệ điều hành phải thường xuyên chuyển ngữ cảnh giữa chúng, dẫn đến chi phí tính toán không cần thiết
Kết quả là tác vụ tổng thể có thể sẽ chậm hơn khi thực thi nó với ThreadPool so với thực thi trực tiếp trong vòng lặp for
Cho rằng việc thực thi các tác vụ liên quan đến CPU với ThreadPool có thể sẽ dẫn đến hiệu suất tương tự hoặc kém hơn, chúng tôi có thể coi cách sử dụng này là một cách chống mẫu. Đó là một ThreadPool sử dụng có thể dễ dàng xác định và phải tránh, e. g. một giải pháp tồi cho vấn đề đồng thời trong Python
- Sử dụng ThreadPool cho các Tác vụ liên kết với CPU là một Anti-pattern
đa xử lý. hồ bơi. Nhóm có lẽ nên được sử dụng thay thế cho các tác vụ liên quan đến CPU. Điều này là do nó sử dụng các quy trình thay vì các luồng và do đó, nó không bị ràng buộc bởi GIL
Bây giờ chúng ta đã biết tại sao việc sử dụng ThreadPool có thể chậm hơn vòng lặp for trong một số trường hợp, hãy xem một ví dụ đã hoạt động
Bối rối với API lớp ThreadPool?
Tải xuống bảng cheat PDF MIỄN PHÍ của tôi
Ví dụ về ThreadPool chậm hơn vòng lặp For
Hãy xem một ví dụ trong đó việc sử dụng ThreadPool có thể chậm hơn so với vòng lặp for
Nhiệm vụ giới hạn CPU trong vòng lặp For
Trước tiên, hãy xác định một tác vụ đơn giản liên kết với CPU để thực thi nhiều lần
Trong trường hợp này, chúng ta có thể bình phương một số. Nghĩa là, được cung cấp một số đầu vào, trả về giá trị bình phương
1
2
3
# thực hiện một số phép toán
def hoạt động[giá trị]:
trả về giá trị**2
Tiếp theo, hãy thực hiện thao tác này nhiều lần, chẳng hạn như một triệu [10.000.000] lần và báo cáo một thông báo khi chúng tôi thực hiện xong. Tức là ta sẽ bình phương các số từ 0 đến 9.999.999
Chúng ta có thể sử dụng cách hiểu danh sách, đó là vòng lặp for Pythonic
1
2
3
4
.. .
# thực hiện một phép toán nhiều lần
giá trị = [hoạt động[i] for i in range[10000000]]
in['xong']
Điều này có thể dễ dàng được viết trực tiếp dưới dạng vòng lặp for;
1
2
3
4
5
6
.. .
# thực hiện một phép toán nhiều lần
giá trị = danh sách[]
cho i trong phạm vi[10000000]:
giá trị. chắp thêm[thao tác[i]]
in['xong']
Hoặc sử dụng hàm map[], hàm này cũng có thể mang tính Pythonic hơn;
1
2
3
4
.. .
# thực hiện một phép toán nhiều lần
giá trị = danh sách[bản đồ[operation, range[10000000]]]
in['xong']
Chúng tôi sẽ gắn bó với việc hiểu danh sách. Ví dụ đầy đủ được liệt kê dưới đây
1
2
3
4
5
6
7
8
9
10
11
12
13
#Trăn Siêu Nhanh. com
# ví dụ về việc thực hiện một nhiệm vụ toán học đơn giản nhiều lần trong vòng lặp for
# thực hiện một số phép toán
def hoạt động[giá trị]:
trả về giá trị**2
#bảo vệ điểm vào
if __name__ == '__main__'.
# thực hiện một phép toán nhiều lần
# giá trị = [hoạt động[i] cho tôi trong phạm vi[1000000]]
giá trị = [hoạt động[i] for i in range[10000000]]
in['xong']
Code chạy nhanh, hoàn thành trong khoảng 3. 2 giây trên hệ thống của tôi
Mất bao lâu để chạy trên hệ thống của bạn?
Hãy cho tôi biết trong phần nhận xét bên dưới.
Tiếp theo, hãy thực hiện đồng thời tác vụ bằng cách sử dụng ThreadPool
Nhiệm vụ giới hạn CPU trong ThreadPool
Chúng ta có thể cập nhật mã từ ví dụ trước để sử dụng ThreadPool
Đây sẽ là một phản mẫu như đã mô tả trước đây, do đó, chúng tôi hy vọng ví dụ này sẽ chạy nhanh như pr chậm hơn so với phiên bản vòng lặp for do chi phí chuyển đổi ngữ cảnh
Đầu tiên, chúng ta có thể tạo một nhóm luồng với một số luồng, trong trường hợp này là 4. Sau đó, chúng ta có thể sử dụng phương thức map[] trên ThreadPool để gửi các tác vụ vào nhóm luồng. Mỗi nhiệm vụ sẽ là bình phương một số, với các số từ 0 đến 9.999.999 được gửi vào nhóm để thực hiện
1
2
3
4
.. .
# thực hiện một phép toán nhiều lần
với Nhóm luồng[4] as pool:
kết quả = nhóm. bản đồ[hoạt động, phạm vi[10000000]]
Ví dụ đầy đủ được liệt kê dưới đây
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Trăn Siêu Nhanh. com
# ví dụ về cách sử dụng nhóm luồng có thể chậm hơn, phản mẫu
từ đa xử lý. nhóm nhập ThreadPool
# thực hiện một số phép toán
def hoạt động[giá trị]:
trả về giá trị**2
#bảo vệ điểm vào
if __name__ == '__main__'.
# thực hiện một phép toán nhiều lần
với ThreadPool[4] as pool:
kết quả = nhóm. bản đồ[hoạt động, phạm vi[10000000]]
in['xong']
Chạy ví dụ bình phương tất cả các số như trước
Trên hệ thống của tôi, phải mất khoảng 3. 3 giây để hoàn thành so với 3. 2 giây được thực hiện với vòng lặp for
Điều này chậm hơn một chút, chậm hơn 100 mili giây hoặc gần bằng tốc độ không mang lại lợi ích gì
Khóa học Python ThreadPool miễn phí
Tải xuống bảng cheat API ThreadPool của tôi và như một phần thưởng, bạn sẽ nhận được quyền truy cập MIỄN PHÍ vào khóa học email 7 ngày của tôi
Khám phá cách sử dụng ThreadPool bao gồm cách định cấu hình số luồng công nhân và cách thực thi các tác vụ không đồng bộ
Tìm hiểu thêm
Tại sao The ThreadPool không chậm hơn nhiều?
Lý do là theo mặc định, map[] sử dụng giá trị đối số "chunksize" khác 1
Nhớ lại rằng đối số "chunksize" cho phương thức map[] kiểm soát việc ánh xạ các tác vụ đã phát hành tới các tác vụ nội bộ được truyền tới các luồng công nhân để thực thi. Nó cho phép các lệnh gọi hàm đã phát hành được nhóm thành các đợt gọi là khối để thực thi, mang lại lợi ích tính toán lớn
Nếu “chunksize” được đặt thành 1, nghĩa là một tác vụ cho mỗi lệnh gọi hàm, thì ví dụ này sẽ chạy chậm hơn rất nhiều
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Trăn Siêu Nhanh. com
# ví dụ về cách sử dụng nhóm luồng có thể chậm hơn, phản mẫu
từ đa xử lý. nhóm nhập ThreadPool
# thực hiện một số phép toán
def hoạt động[giá trị]:
trả về giá trị**2
#bảo vệ điểm vào
if __name__ == '__main__'.
# thực hiện một phép toán nhiều lần
với ThreadPool[4] as pool:
kết quả = nhóm. bản đồ[hoạt động, phạm vi[10000000], chunksize=1]
in['xong']
Chạy ví dụ bình phương tất cả các số như trước, nhưng nó chậm hơn đáng kể
Trên hệ thống của tôi, phải mất khoảng 44. 3 giây để hoàn thành, so với 3. 2 giây cho phiên bản for-loop
Đó là 41. 1 giây dài hơn hoặc 13. chậm hơn 8 lần
Một lần nữa, lý do thiếu cải tiến là do tác vụ là tác vụ gắn với CPU và ThreadPool sử dụng các luồng tuân theo GIL, nghĩa là chỉ một luồng có thể thực thi tại một thời điểm và hệ điều hành sẽ chuyển đổi ngữ cảnh giữa chúng.
Choáng ngợp trước các API đồng thời của python?
Để tìm sự giải thoát, hãy tải xuống Bản đồ tư duy về đồng thời Python MIỄN PHÍ của tôi
Câu hỏi thường gặp
Phần này trả lời một số câu hỏi thường gặp về ví dụ này
Điều gì xảy ra nếu chúng ta sử dụng nhiều chủ đề hơn trong ThreadPool?
Sử dụng nhiều luồng hơn sẽ không cải thiện hiệu suất vì lý do tương tự như việc sử dụng 4 luồng không tăng tốc độ thực thi tác vụ
GIL đảm bảo chỉ có một luồng thực thi các lệnh tại một thời điểm và hệ điều hành sẽ chuyển ngữ cảnh giữa các tác vụ, bổ sung thêm chi phí đáng kể cho tác vụ tổng thể
Điều gì xảy ra nếu chúng ta sử dụng một luồng trong ThreadPool?
ThreadPool sẽ vẫn chậm hơn vòng lặp for ngay cả khi nhóm luồng có một luồng
Lý do là vì tất cả các chi phí bổ sung trong nhóm luồng để đóng gói từng tác vụ bằng cách sử dụng các lớp bên trong và các lệnh gọi hàm bổ sung khi tác vụ nảy xung quanh bên trong nhóm luồng để thực thi
Đây là lý do tại sao ngay cả khi sử dụng Nhóm đa xử lý cho một tác vụ đơn giản như vậy cũng có thể không giúp tăng tốc, ít nhất là đối với ví dụ này
Worker Threads trong ThreadPool có chạy trên các lõi khác nhau không?
Chắc là không
Hệ điều hành xác định mã nào sẽ chạy trên mỗi lõi CPU trong hệ thống của bạn
Vì chỉ có một luồng có thể thực thi tại một thời điểm trong ví dụ này nên rất có khả năng một lõi CPU sẽ được sử dụng
Việc sử dụng nhóm đa xử lý sẽ tăng tốc ví dụ này chứ?
Có lẽ
Thao tác rất đơn giản và chúng tôi đang thực hiện hàng triệu lần
Nếu chúng ta đẩy các tác vụ này vào một Nhóm đa xử lý bằng cách sử dụng map[], trước tiên chúng ta sẽ phải điều chỉnh đối số chunksize xác định ánh xạ các tác vụ mà chúng ta gửi tới các tác vụ nội bộ được tuần tự hóa và truyền đến các quy trình worker trong nhóm
Sau đó, việc thực thi các tác vụ trong nhóm quy trình sẽ thêm chi phí hoạt động, trước tiên là để gói bổ sung các tác vụ bằng cách sử dụng các đối tượng bên trong và các lời gọi hàm bổ sung cần được thực hiện, và thứ hai là cho giao tiếp giữa các quy trình cần thiết để chia sẻ các tác vụ và kết quả của chúng