quyết định giữa quy trình con, quy trình đa xử lý và luồng trong Python?


110

Tôi muốn song song hóa chương trình Python của mình để nó có thể sử dụng nhiều bộ xử lý trên máy mà nó chạy. Việc song song hóa của tôi rất đơn giản, trong đó tất cả các "luồng" song song của chương trình là độc lập và ghi đầu ra của chúng vào các tệp riêng biệt. Tôi không cần các luồng để trao đổi thông tin nhưng tôi bắt buộc phải biết khi nào các luồng kết thúc vì một số bước trong đường dẫn của tôi phụ thuộc vào đầu ra của chúng.

Tính di động rất quan trọng, ở chỗ tôi muốn nó chạy trên bất kỳ phiên bản Python nào trên Mac, Linux và Windows. Với những ràng buộc này, mô-đun Python nào thích hợp nhất để thực hiện điều này? Tôi đang cố gắng quyết định giữa luồng, quy trình con và đa xử lý, dường như tất cả đều cung cấp chức năng liên quan.

Bất kỳ suy nghĩ về điều này? Tôi muốn giải pháp đơn giản nhất có thể di động.


Liên quan: stackoverflow.com/questions/1743293/… (đọc câu trả lời của tôi ở đó để xem tại sao các chủ đề không phải là bộ khởi động cho mã Python thuần túy)

1
"Bất kỳ phiên bản Python nào" là FAR quá mơ hồ. Python 2.3? 1.x? 3.x? Nó chỉ đơn giản là một điều kiện không thể để thỏa mãn.
detly

Câu trả lời:


64

multiprocessinglà một loại mô-đun dao tuyệt vời của quân đội Thụy Sĩ. Nó tổng quát hơn các luồng, vì bạn thậm chí có thể thực hiện các phép tính từ xa. Do đó, đây là mô-đun tôi khuyên bạn nên sử dụng.

Các subprocessmô-đun cũng sẽ cho phép bạn khởi động nhiều quy trình, nhưng tôi thấy nó là kém thuận lợi để sử dụng hơn so với các mô-đun đa mới.

Các chủ đề nổi tiếng là tinh tế và với CPython, bạn thường bị giới hạn ở một lõi, với chúng (mặc dù, như đã lưu ý trong một trong các nhận xét, Khóa thông dịch viên toàn cầu (GIL) có thể được phát hành bằng mã C được gọi từ mã Python) .

Tôi tin rằng hầu hết các chức năng của ba mô-đun bạn trích dẫn có thể được sử dụng theo cách độc lập với nền tảng. Về mặt tính di động, hãy lưu ý rằng multiprocessingchỉ có trong tiêu chuẩn kể từ Python 2.6 (mặc dù vậy, phiên bản dành cho một số phiên bản Python cũ hơn đã tồn tại). Nhưng đó là một mô-đun tuyệt vời!


1
cho một nhiệm vụ, tôi vừa sử dụng mô-đun "đa xử lý" và phương thức pool.map () của nó. miếng bánh !
kmonsoor

Có phải một thứ giống như Cần tây cũng đang được xem xét? Tại sao có hay không?
user3245268

Theo như tôi có thể nói Celery tham gia nhiều hơn (bạn phải cài đặt một số nhà môi giới tin nhắn), nhưng đó là một lựa chọn có lẽ nên được xem xét, tùy thuộc vào vấn đề hiện tại.
Eric O Lebigot

186

Đối với tôi, điều này thực sự khá đơn giản:

Các tiến trình con tùy chọn:

subprocessđể chạy các tệp thực thi khác --- về cơ bản nó là một trình bao bọc xung quanh os.fork()os.execve()với một số hỗ trợ cho hệ thống ống nước tùy chọn (thiết lập PIPE đến và đi từ các quy trình con. Rõ ràng là bạn có thể có các cơ chế truyền thông liên quá trình (IPC) khác, chẳng hạn như ổ cắm hoặc Posix hoặc Bộ nhớ chia sẻ SysV. Nhưng bạn sẽ bị giới hạn ở bất kỳ giao diện nào và các kênh IPC được hỗ trợ bởi các chương trình bạn đang gọi.

Thông thường, một người sử dụng bất kỳ subprocessđồng bộ nào --- chỉ cần gọi một số tiện ích bên ngoài và đọc lại kết quả đầu ra của nó hoặc chờ hoàn thành (có thể đọc kết quả của nó từ một tệp tạm thời hoặc sau khi nó được đăng chúng lên cơ sở dữ liệu nào đó).

Tuy nhiên, người ta có thể sinh ra hàng trăm quy trình con và thăm dò chúng. Classh tiện ích yêu thích cá nhân của riêng tôi thực hiện chính xác điều đó. Nhược điểm lớn nhất của subprocessmô-đun là hỗ trợ I / O thường bị chặn. Có một bản dự thảo PEP-3145 để sửa lỗi đó trong một số phiên bản tương lai của Python 3.x và một asyncproc thay thế (Cảnh báo dẫn đến quyền tải xuống, không phải bất kỳ loại tài liệu nào cũng như README). Tôi cũng nhận thấy rằng việc nhập fcntlvà thao tác Popentrực tiếp các bộ mô tả tệp PIPE của bạn tương đối dễ dàng --- mặc dù tôi không biết liệu điều này có khả dụng với các nền tảng không phải UNIX hay không.

(Cập nhật: ngày 7 tháng 8 năm 2019: Hỗ trợ Python 3 cho các quy trình con ayncio : asyncio Subprocessses )

subprocess hầu như không có hỗ trợ xử lý sự kiện ... mặc dù bạn có thể sử dụng signalmô-đun và các tín hiệu UNIX / Linux kiểu cũ đơn giản --- giết chết các quy trình của bạn một cách nhẹ nhàng, như ban đầu.

Các đa xử tùy chọn:

multiprocessingđể chạy các chức năng trong mã (Python) hiện có của bạn với sự hỗ trợ cho các giao tiếp linh hoạt hơn giữa các quy trình này. Đặc biệt, tốt nhất là bạn nên xây dựng multiprocessingIPC của mình xung quanh các Queueđối tượng của mô-đun nếu có thể, nhưng bạn cũng có thể sử dụng Eventcác đối tượng và nhiều tính năng khác (một số trong số đó, có lẽ, được xây dựng xung quanh mmaphỗ trợ trên các nền tảng mà sự hỗ trợ đó là đủ).

multiprocessingMô-đun của Python nhằm cung cấp các giao diện và tính năng rất giống threading trong khi cho phép CPython mở rộng quy mô xử lý của bạn giữa nhiều CPU / lõi bất chấp GIL (Global Interpreter Lock). Nó tận dụng tất cả nỗ lực khóa và đồng tiền hóa SMP chi tiết đã được thực hiện bởi các nhà phát triển nhân hệ điều hành của bạn.

Các luồng tùy chọn:

threadingcho một phạm vi tương đối hẹp của các ứng dụng đó là I / O bị ràng buộc (không cần phải quy mô trên nhiều lõi CPU) và đó được hưởng lợi từ độ trễ cực thấp và chuyển đổi chi phí của chuyển đổi chủ đề (với bộ nhớ lõi được chia sẻ) quá trình vs. / chuyển đổi ngữ cảnh. Trên Linux, đây gần như là tập hợp trống (thời gian chuyển đổi quy trình Linux rất gần với chuyển mạch luồng của nó).

threadingmắc phải hai nhược điểm lớn trong Python .

Tất nhiên, một trong những cách triển khai cụ thể --- chủ yếu ảnh hưởng đến CPython. Đó là GIL. Phần lớn, hầu hết các chương trình CPython sẽ không được hưởng lợi từ sự sẵn có của hơn hai CPU (lõi) và thường thì hiệu suất sẽ bị ảnh hưởng bởi tranh chấp khóa GIL.

Vấn đề lớn hơn không phải là việc triển khai cụ thể, là các luồng chia sẻ cùng một bộ nhớ, bộ xử lý tín hiệu, bộ mô tả tệp và một số tài nguyên hệ điều hành khác. Do đó, lập trình viên phải cực kỳ cẩn thận về việc khóa đối tượng, xử lý ngoại lệ và các khía cạnh khác của mã của họ, những thứ vừa tinh vi vừa có thể giết chết, đình trệ hoặc bế tắc toàn bộ quá trình (bộ luồng).

Bằng cách so sánh, multiprocessingmô hình cung cấp cho mỗi quy trình bộ nhớ riêng, bộ mô tả tệp, v.v. Một sự cố hoặc ngoại lệ không được xử lý trong bất kỳ một trong số chúng sẽ chỉ giết tài nguyên đó và xử lý mạnh mẽ sự biến mất của quy trình con hoặc anh chị em có thể dễ dàng hơn đáng kể so với gỡ lỗi, cô lập và khắc phục hoặc giải quyết các vấn đề tương tự trong chuỗi.

  • (Lưu ý: việc sử dụng threadingvới các hệ thống Python chính, chẳng hạn như NumPy , có thể ít bị tranh chấp GIL hơn đáng kể so với hầu hết mã Python của riêng bạn. Đó là bởi vì chúng đã được thiết kế đặc biệt để làm như vậy; các phần gốc / nhị phân của NumPy, ví dụ: sẽ giải phóng GIL khi điều đó an toàn).

Các xoắn tùy chọn:

Cũng cần lưu ý rằng Twisted cung cấp một giải pháp thay thế khác vừa thanh lịch nhưng rất khó hiểu . Về cơ bản, trước nguy cơ đơn giản hóa quá mức đến mức người hâm mộ Twisted có thể xông vào nhà tôi với những cây ném và ngọn đuốc, Twisted cung cấp tính năng đa tác vụ hợp tác theo hướng sự kiện trong bất kỳ quy trình (đơn lẻ) nào.

Để hiểu làm thế nào điều này có thể xảy ra, người ta nên đọc về các tính năng của select()(có thể được xây dựng xung quanh lệnh select () hoặc thăm dò () hoặc các lệnh gọi hệ điều hành tương tự). Về cơ bản, tất cả đều được thúc đẩy bởi khả năng yêu cầu HĐH ở chế độ ngủ chờ bất kỳ hoạt động nào trên danh sách các bộ mô tả tệp hoặc một số thời gian chờ.

Đánh thức từ mỗi lệnh gọi tới select()là một sự kiện --- hoặc một sự kiện liên quan đến đầu vào có sẵn (có thể đọc được) trên một số ổ cắm hoặc bộ mô tả tệp hoặc không gian đệm trở nên khả dụng trên một số bộ mô tả hoặc ổ cắm (có thể ghi) khác, một số điều kiện ngoại lệ (TCP chẳng hạn như gói PUSH'd ngoài băng) hoặc TIMEOUT.

Do đó, mô hình lập trình Twisted được xây dựng xung quanh việc xử lý các sự kiện này sau đó lặp lại trên trình xử lý "chính" kết quả, cho phép nó gửi các sự kiện đến trình xử lý của bạn.

Cá nhân tôi nghĩ về cái tên, Twisted như gợi liên tưởng đến mô hình lập trình ... vì cách tiếp cận vấn đề của bạn, theo một nghĩa nào đó, phải "xoắn" từ trong ra ngoài. Thay vì quan niệm chương trình của bạn như một chuỗi hoạt động trên dữ liệu đầu vào và đầu ra hoặc kết quả, bạn đang viết chương trình của mình dưới dạng dịch vụ hoặc daemon và xác định cách nó phản ứng với các sự kiện khác nhau. (Trên thực tế, "vòng lặp chính" cốt lõi của chương trình Twisted là (thường là? Luôn luôn?) A reactor()).

Những thách thức lớn đối với việc sử dụng Twisted bao gồm việc xoay chuyển tâm trí của bạn xung quanh mô hình điều khiển sự kiện và cũng tránh sử dụng bất kỳ thư viện lớp hoặc bộ công cụ nào không được viết để hợp tác trong khuôn khổ Twisted. Đây là lý do tại sao Twisted cung cấp các mô-đun của riêng mình để xử lý giao thức SSH, cho các lời nguyền, và các chức năng của quy trình con / Popen của riêng nó, và nhiều mô-đun và trình xử lý giao thức khác, thoạt đầu, dường như sẽ sao chép mọi thứ trong các thư viện chuẩn Python.

Tôi nghĩ sẽ rất hữu ích nếu hiểu về Twisted ở mức độ khái niệm ngay cả khi bạn không bao giờ có ý định sử dụng nó. Nó có thể cung cấp thông tin chi tiết về hiệu suất, tranh chấp và xử lý sự kiện trong quá trình xử lý luồng, xử lý đa quy trình và thậm chí cả quy trình phụ của bạn cũng như bất kỳ xử lý phân tán nào mà bạn thực hiện.

( Lưu ý: Các phiên bản mới hơn của Python 3.x bao gồm các tính năng asyncio (I / O không đồng bộ) như async def , trình trang trí @ async.coroutine và từ khóa await , và mang lại lợi nhuận từ hỗ trợ trong tương lai . Tất cả những điều này gần giống với Xoắn từ góc độ quy trình (đa nhiệm hợp tác)). (Để biết trạng thái hiện tại của hỗ trợ Twisted cho Python 3, hãy xem: https://twistedmatrix.com/documents/current/core/howto/python3.html )

Các phân phối tùy chọn:

Tuy nhiên, một lĩnh vực xử lý khác mà bạn chưa từng hỏi đến, nhưng điều đáng xem xét, đó là xử lý phân tán . Có nhiều công cụ và khuôn khổ Python để xử lý phân tán và tính toán song song. Cá nhân tôi nghĩ rằng cái dễ sử dụng nhất là cái ít được coi là có trong không gian đó.

Việc xây dựng quá trình xử lý phân tán xung quanh Redis gần như là chuyện nhỏ . Toàn bộ kho lưu trữ khóa có thể được sử dụng để lưu trữ các đơn vị công việc và kết quả, DANH SÁCH Redis có thể được sử dụng Queue()như đối tượng tương tự và hỗ trợ PUB / SUB có thể được sử dụng để Eventxử lý giống như vậy. Bạn có thể băm các khóa của mình và sử dụng các giá trị, được sao chép qua một cụm bản sao Redis lỏng lẻo, để lưu trữ cấu trúc liên kết và ánh xạ mã thông báo băm để cung cấp hàm băm nhất quán và dự phòng để mở rộng vượt quá khả năng của bất kỳ bản sao đơn lẻ nào để điều phối các nhân viên của bạn và sắp xếp dữ liệu (pickled, JSON, BSON hoặc YAML) trong số đó.

Tất nhiên khi bạn bắt đầu xây dựng một giải pháp quy mô lớn hơn và phức tạp hơn xung quanh Redis, bạn đang triển khai lại nhiều tính năng đã được giải quyết bằng cách sử dụng Celery , Apache SparkHadoop , Zookeeper , etcd , Cassandra , v.v. Tất cả chúng đều có các mô-đun để truy cập Python vào các dịch vụ của họ.

[Cập nhật: Một số tài nguyên cần xem xét nếu bạn đang xem xét Python để tính toán chuyên sâu trên các hệ thống phân tán: IPython ParallelPySpark . Mặc dù đây là những hệ thống máy tính phân tán có mục đích chung, nhưng chúng là những hệ thống phân tích và khoa học dữ liệu đặc biệt dễ tiếp cận và phổ biến].

Phần kết luận

Ở đó bạn có hàng loạt các lựa chọn xử lý thay thế cho Python, từ đơn luồng, với các lệnh gọi đồng bộ đơn giản đến các quy trình con, nhóm các quy trình con được thăm dò ý kiến, xử lý đa luồng và đa xử lý, đa tác vụ hợp tác theo hướng sự kiện và ra ngoài để xử lý phân tán.


1
Tuy nhiên, khó có thể sử dụng đa xử lý với các lớp / OOP.
Tjorriemorrie

2
@Tjorriemorrie: Tôi đoán rằng ý của bạn là thật khó để gửi các lệnh gọi phương thức đến các phiên bản của đối tượng có thể nằm trong các quy trình khác. Tôi gợi ý rằng đây là vấn đề tương tự bạn gặp phải với các luồng, nhưng dễ nhìn thấy hơn (thay vì mỏng manh và phải tuân theo các điều kiện chủng tộc khó hiểu). Tôi nghĩ rằng cách tiếp cận được đề xuất sẽ là sắp xếp để tất cả các điều phối như vậy xảy ra thông qua các đối tượng Hàng đợi, hoạt động đơn luồng, đa luồng và xuyên suốt các quy trình. (Với một số triển khai Redis hoặc Celery Queue, thậm chí trên một cụm nút)
Jim Dennis

2
Đây là một câu trả lời thực sự tốt. Tôi ước nó nằm trong phần giới thiệu về đồng thời trong tài liệu Python3.
root-11

1
@ root-11, bạn có thể đề xuất nó với những người bảo trì tài liệu; Tôi đã xuất bản nó ở đây để sử dụng miễn phí. Bạn và họ được hoan nghênh sử dụng nó, toàn bộ hoặc từng phần.
Jim Dennis

"Đối với tôi, điều này thực sự khá đơn giản:" Hãy yêu nó. cảm ơn rất nhiều
jerome

5

Trong một trường hợp tương tự, tôi đã chọn các quy trình riêng biệt và một chút ổ cắm mạng máng giao tiếp cần thiết. Nó có tính di động cao và khá đơn giản để thực hiện bằng python, nhưng có lẽ không đơn giản hơn (trong trường hợp của tôi, tôi cũng có một hạn chế khác: giao tiếp với các quy trình khác được viết bằng C ++).

Trong trường hợp của bạn, tôi có thể sẽ sử dụng đa quy trình, vì các luồng python, ít nhất là khi sử dụng CPython, không phải là các luồng thực. Chà, chúng là các luồng hệ thống gốc nhưng các mô-đun C được gọi từ Python có thể giải phóng GIL và cho phép các luồng khác chạy khi gọi mã chặn.


4

Để sử dụng nhiều bộ xử lý trong CPython, lựa chọn duy nhất của bạn là multiprocessingmô-đun. CPython giữ một khóa bên trong của nó ( GIL ), ngăn chặn các luồng trên các cp khác hoạt động song song. Các multiprocessingmô-đun tạo ra quy trình mới (như subprocess) và quản lý thông tin liên lạc giữa chúng.


5
Điều đó không hoàn toàn đúng, AFAIK bạn có thể phát hành GIL bằng cách sử dụng API C và có những triển khai Python khác như IronPython hoặc Jython không bị những hạn chế như vậy. Tôi không phản đối mặc dù.
Bastien Léonard

1

Shell ra ngoài và để unix thực hiện công việc của bạn:

sử dụng iterpipes để bọc quy trình con và sau đó:

Từ trang của Ted Ziuba

INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM quy trình song song

HOẶC LÀ

Gnu Parallel cũng sẽ phục vụ

Bạn đi chơi với GIL trong khi bạn gửi các chàng trai hậu vệ ra ngoài để làm công việc đa nhân cách của bạn.


6
"Tính di động rất quan trọng, ở chỗ tôi muốn điều này chạy trên bất kỳ phiên bản Python nào trên Mac, Linux và Windows."
detly

Với giải pháp này, bạn có thể tương tác nhiều lần với công việc không? Bạn có thể làm điều này trong đa quy trình, nhưng tôi không nghĩ như vậy trong quy trình con.
abalter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.