multiprocessing: chia sẻ một đối tượng chỉ đọc lớn giữa các tiến trình?


107

Các quy trình con có được tạo ra thông qua các đối tượng chia sẻ đa xử lý được tạo trước đó trong chương trình không?

Tôi có thiết lập sau:

do_some_processing(filename):
    for line in file(filename):
        if line.split(',')[0] in big_lookup_object:
            # something here

if __name__ == '__main__':
    big_lookup_object = marshal.load('file.bin')
    pool = Pool(processes=4)
    print pool.map(do_some_processing, glob.glob('*.data'))

Tôi đang tải một số đối tượng lớn vào bộ nhớ, sau đó tạo một nhóm công nhân cần sử dụng đối tượng lớn đó. Đối tượng lớn được truy cập ở chế độ chỉ đọc, tôi không cần chuyển các sửa đổi của nó giữa các quá trình.

Câu hỏi của tôi là: đối tượng lớn có được tải vào bộ nhớ dùng chung không, như nó sẽ xảy ra nếu tôi tạo một quy trình trong unix / c, hay mỗi quy trình tải bản sao của đối tượng lớn?

Cập nhật: để làm rõ thêm - big_lookup_object là một đối tượng tra cứu được chia sẻ. Tôi không cần phải chia nhỏ ra và xử lý riêng. Tôi cần giữ một bản sao của nó. Công việc mà tôi cần chia nó là đọc nhiều tệp lớn khác và tra cứu các mục trong tệp lớn đó dựa trên đối tượng tra cứu.

Cập nhật thêm: cơ sở dữ liệu là một giải pháp tốt, memcached có thể là giải pháp tốt hơn và tệp trên đĩa (kệ hoặc dbm) có thể còn tốt hơn. Trong câu hỏi này, tôi đặc biệt quan tâm đến một giải pháp trong bộ nhớ. Đối với giải pháp cuối cùng, tôi sẽ sử dụng hadoop, nhưng tôi muốn xem liệu tôi cũng có thể có phiên bản trong bộ nhớ cục bộ hay không.


mã của bạn như được viết sẽ gọi marshal.loadcho cha mẹ và cho mỗi con (mỗi tiến trình nhập mô-đun).
jfs

Bạn đúng, đã sửa.
Diễu hành

Đối với "bộ nhớ cục bộ" và nếu bạn muốn tránh sao chép, những điều sau đây có thể hữu ích docs.python.org/library/…
jfs

chia sẻ không. các quy trình được tạo ra (ví dụ như fork hoặc executive) là một bản sao chính xác của quá trình gọi ... nhưng trong bộ nhớ khác nhau. Để một quá trình giao tiếp với một quá trình khác, bạn cần giao tiếp giữa các quá trình hoặc đọc / ghi IPC vào một số vị trí bộ nhớ dùng chung .
ron

Câu trả lời:


50

"Các quy trình con có được tạo ra thông qua các đối tượng chia sẻ đa xử lý được tạo trước đó trong chương trình không?"

Không (python trước 3.8) và Có trong 3.8 ( https://docs.python.org/3/library/multiprocessing.shared_memory.html#module-multiprocessing.shared_memory )

Các tiến trình có không gian bộ nhớ độc lập.

Giải pháp 1

Để tận dụng tốt nhất một cấu trúc lớn với nhiều công nhân, hãy làm điều này.

  1. Viết mỗi worker như một "bộ lọc" - đọc kết quả trung gian từ stdin, thực hiện công việc, ghi kết quả trung gian trên stdout.

  2. Kết nối tất cả các công nhân như một đường ống:

    process1 <source | process2 | process3 | ... | processn >result

Mỗi quá trình đọc, hoạt động và ghi.

Điều này rất hiệu quả vì tất cả các quy trình đang chạy đồng thời. Việc ghi và đọc truyền trực tiếp qua bộ đệm được chia sẻ giữa các quá trình.


Giải pháp 2

Trong một số trường hợp, bạn có cấu trúc phức tạp hơn - thường là cấu trúc "quạt ra". Trong trường hợp này, bạn có cha hoặc mẹ có nhiều con.

  1. Nguồn gốc mở dữ liệu. Cha mẹ cho một số trẻ em.

  2. Phụ huynh đọc nguồn, chuyển các phần của nguồn ra cho từng con chạy đồng thời.

  3. Khi phụ huynh đến cuối, đóng đường ống. Con nhận được cuối tệp và kết thúc bình thường.

Các phần trẻ dễ viết vì mỗi trẻ chỉ đọc sys.stdin.

Cha mẹ có một chút khéo léo trong việc sinh sản tất cả các con và giữ lại các đường ống đúng cách, nhưng nó không quá tệ.

Fan-in là cấu trúc ngược lại. Một số quy trình đang chạy độc lập cần phải xen kẽ các đầu vào của chúng vào một quy trình chung. Người sưu tầm không dễ viết vì phải đọc từ nhiều nguồn.

Việc đọc từ nhiều đường ống được đặt tên thường được thực hiện bằng cách sử dụng selectmô-đun để xem đường ống nào có đầu vào đang chờ xử lý.


Giải pháp 3

Tra cứu chia sẻ là định nghĩa của cơ sở dữ liệu.

Giải pháp 3A - tải cơ sở dữ liệu. Để công nhân xử lý dữ liệu trong cơ sở dữ liệu.

Giải pháp 3B - tạo một máy chủ rất đơn giản bằng cách sử dụng werkzeug (hoặc tương tự) để cung cấp các ứng dụng WSGI đáp ứng HTTP GET để nhân viên có thể truy vấn máy chủ.


Giải pháp 4

Đối tượng hệ thống tệp được chia sẻ. Unix OS cung cấp các đối tượng bộ nhớ dùng chung. Đây chỉ là các tệp được ánh xạ vào bộ nhớ để việc trao đổi I / O được thực hiện thay vì các lần đọc được đệm theo quy ước.

Bạn có thể làm điều này từ ngữ cảnh Python theo một số cách

  1. Viết chương trình khởi động (1) chia đối tượng khổng lồ ban đầu của bạn thành các đối tượng nhỏ hơn và (2) khởi động các công nhân, mỗi đối tượng có một đối tượng nhỏ hơn. Các đối tượng nhỏ hơn có thể là các đối tượng Python được chọn để tiết kiệm một chút thời gian đọc tệp.

  2. Viết một chương trình khởi động (1) đọc đối tượng khổng lồ ban đầu của bạn và viết một tệp có cấu trúc trang, mã byte bằng cách sử dụng các seekthao tác để đảm bảo rằng các phần riêng lẻ dễ tìm thấy bằng các thao tác tìm kiếm đơn giản. Đây là những gì một công cụ cơ sở dữ liệu làm - chia nhỏ dữ liệu thành các trang, làm cho mỗi trang dễ dàng tìm thấy thông qua a seek.

    Nhân viên sinh sản có quyền truy cập vào tệp có cấu trúc trang lớn này. Mỗi công nhân có thể tìm đến các bộ phận liên quan và thực hiện công việc của họ ở đó.


Quy trình của tôi không thực sự phù hợp; chúng đều giống nhau, chỉ xử lý các phần dữ liệu khác nhau.
Diễu hành

Chúng thường có thể được cấu trúc dưới dạng bộ lọc. Họ đọc phần dữ liệu của họ, thực hiện công việc của họ và viết kết quả của họ để xử lý sau.
S.Lott

Tôi thích giải pháp của bạn, nhưng điều gì xảy ra với I / O bị chặn? Điều gì sẽ xảy ra nếu cha mẹ chặn việc đọc / viết từ / đến một trong những đứa con của nó? Chọn thông báo cho bạn rằng bạn có thể viết, nhưng nó không cho biết bao nhiêu. Đọc cũng vậy.
Cristian Ciupitu

Đây là những quá trình riêng biệt - cha mẹ và con cái không can thiệp vào nhau. Mỗi byte được tạo ra ở một đầu của đường ống sẽ ngay lập tức có sẵn ở đầu kia để tiêu thụ - một đường ống là bộ đệm dùng chung. Không chắc câu hỏi của bạn có ý nghĩa gì trong bối cảnh này.
S.Lott

Tôi có thể xác minh những gì S.Lott đã nói. Tôi cần các thao tác tương tự được thực hiện trên một tệp duy nhất. Vì vậy, worker đầu tiên đã chạy chức năng của nó trên mọi dòng có số% 2 == 0 và lưu nó vào một tệp, đồng thời gửi các dòng khác đến quy trình nối tiếp theo (cùng một tập lệnh). Thời gian chạy đã giảm một nửa. Nó hơi hacky một chút, nhưng chi phí nhẹ hơn nhiều so với map / poop trong mô-đun đa xử lý.
Vince vào

36

Các quy trình con có được tạo ra thông qua các đối tượng chia sẻ đa xử lý được tạo trước đó trong chương trình không?

Nó phụ thuộc. Đối với các biến chỉ đọc toàn cục, nó thường có thể được coi là như vậy (ngoài bộ nhớ được sử dụng), nếu không thì không.

tài liệu của multiprocessing cho biết:

Better to inherit than pickle/unpickle

Trên Windows, nhiều loại từ đa xử lý cần được chọn để các quy trình con có thể sử dụng chúng. Tuy nhiên, thường nên tránh gửi các đối tượng được chia sẻ đến các quy trình khác bằng cách sử dụng đường ống hoặc hàng đợi. Thay vào đó, bạn nên sắp xếp chương trình để một tiến trình cần quyền truy cập vào tài nguyên được chia sẻ ở nơi khác có thể kế thừa nó từ một quy trình tổ tiên.

Explicitly pass resources to child processes

Trên Unix, một quy trình con có thể sử dụng tài nguyên được chia sẻ được tạo ra trong quy trình mẹ bằng cách sử dụng tài nguyên chung. Tuy nhiên, tốt hơn là chuyển đối tượng làm đối số cho phương thức khởi tạo cho tiến trình con.

Ngoài việc làm cho mã (có khả năng) tương thích với Windows, điều này cũng đảm bảo rằng miễn là tiến trình con vẫn còn sống, đối tượng sẽ không bị thu thập rác trong tiến trình mẹ. Điều này có thể quan trọng nếu một số tài nguyên được giải phóng khi đối tượng là rác được thu thập trong quy trình mẹ.

Global variables

Hãy nhớ rằng nếu mã chạy trong một tiến trình con cố gắng truy cập một biến toàn cục, thì giá trị mà nó thấy (nếu có) có thể không giống với giá trị trong tiến trình mẹ tại thời điểm mà Process.start () được gọi .

Thí dụ

Trên Windows (một CPU):

#!/usr/bin/env python
import os, sys, time
from multiprocessing import Pool

x = 23000 # replace `23` due to small integers share representation
z = []    # integers are immutable, let's try mutable object

def printx(y):
    global x
    if y == 3:
       x = -x
    z.append(y)
    print os.getpid(), x, id(x), z, id(z) 
    print y
    if len(sys.argv) == 2 and sys.argv[1] == "sleep":
       time.sleep(.1) # should make more apparant the effect

if __name__ == '__main__':
    pool = Pool(processes=4)
    pool.map(printx, (1,2,3,4))

Với sleep:

$ python26 test_share.py sleep
2504 23000 11639492 [1] 10774408
1
2564 23000 11639492 [2] 10774408
2
2504 -23000 11639384 [1, 3] 10774408
3
4084 23000 11639492 [4] 10774408
4

Không có sleep:

$ python26 test_share.py
1148 23000 11639492 [1] 10774408
1
1148 23000 11639492 [1, 2] 10774408
2
1148 -23000 11639324 [1, 2, 3] 10774408
3
1148 -23000 11639324 [1, 2, 3, 4] 10774408
4

6
Huh? Làm thế nào để z được chia sẻ qua các quá trình ??
cbare

4
@cbare: Câu hỏi hay! z trên thực tế không được chia sẻ, vì đầu ra với chế độ ngủ hiển thị. Sản lượng mà không show ngủ mà một đơn xử lý quá trình (PID = 1148) tất cả các công trình; những gì chúng ta thấy trong ví dụ cuối cùng là giá trị của z cho quá trình đơn lẻ này.
Eric O Lebigot

Câu trả lời này cho thấy rằng zkhông được chia sẻ. Do đó, điều này trả lời cho câu hỏi: "không, ít nhất trong Windows, biến cha không được chia sẻ giữa các biến con".
Eric O Lebigot

@EOL: về mặt kỹ thuật thì bạn đúng nhưng trên thực tế nếu dữ liệu chỉ đọc (không giống như ztrường hợp) thì nó có thể được coi là chia sẻ.
jfs

Chỉ cần làm rõ, tuyên bố Hãy nhớ rằng nếu mã chạy trong một tiến trình con cố gắng truy cập vào một biến toàn cục ... trong tài liệu 2.7 đề cập đến Python chạy trong Windows.
dùng1071847

28

S.Lott đúng. Các phím tắt đa xử lý của Python cung cấp cho bạn một bộ nhớ riêng biệt, được nhân đôi.

Trên hầu hết các hệ thống * nix os.fork(), trên thực tế , việc sử dụng lệnh gọi cấp thấp hơn sẽ cung cấp cho bạn bộ nhớ sao chép khi ghi, có thể là những gì bạn đang nghĩ. AFAIK, về lý thuyết, trong các chương trình đơn giản nhất có thể, bạn có thể đọc từ dữ liệu đó mà không bị sao chép.

Tuy nhiên, mọi thứ không hoàn toàn đơn giản trong trình thông dịch Python. Dữ liệu đối tượng và siêu dữ liệu được lưu trữ trong cùng một phân đoạn bộ nhớ, vì vậy ngay cả khi đối tượng không bao giờ thay đổi, một cái gì đó giống như bộ đếm tham chiếu cho đối tượng đó đang được tăng lên sẽ gây ra ghi bộ nhớ và do đó là một bản sao. Hầu như bất kỳ chương trình Python nào đang làm nhiều hơn "print 'hello'" sẽ gây ra sự gia tăng số lượng tham chiếu, vì vậy bạn có thể sẽ không bao giờ nhận ra lợi ích của copy-on-write.

Ngay cả khi ai đó đã quản lý để hack một giải pháp bộ nhớ chia sẻ bằng Python, việc cố gắng điều phối việc thu thập rác trên các quy trình có thể sẽ khá khó khăn.


3
Trong trường hợp đó, chỉ vùng bộ nhớ của số lượng tham chiếu sẽ được sao chép, không nhất thiết là dữ liệu chỉ đọc lớn, phải không?
kawing-chiu

7

Nếu bạn đang chạy dưới Unix, chúng có thể chia sẻ cùng một đối tượng, do cách thức hoạt động của fork (tức là, các tiến trình con có bộ nhớ riêng biệt nhưng nó là copy-on-write, vì vậy nó có thể được chia sẻ miễn là không ai sửa đổi nó). Tôi đã thử những cách sau:

import multiprocessing

x = 23

def printx(y):
    print x, id(x)
    print y

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(printx, (1,2,3,4))

và nhận được kết quả sau:

$ ./mtest.py
23 22995656
1
23 22995656
2
23 22995656
3
23 22995656
4

Tất nhiên điều này không chứng minh rằng một bản sao chưa được tạo ra, nhưng bạn có thể xác minh điều đó trong trường hợp của mình bằng cách xem kết quả đầu ra psđể xem mỗi quy trình con đang sử dụng bao nhiêu bộ nhớ thực.


2
Còn người thu gom rác thì sao? Điều gì xảy ra khi nó chạy? Bố cục bộ nhớ không thay đổi?
Cristian Ciupitu

Đó là một mối quan tâm hợp lệ. Liệu nó có ảnh hưởng đến Parand hay không sẽ phụ thuộc vào cách anh ta sử dụng tất cả những thứ này và mã này phải đáng tin cậy như thế nào. Nếu nó không hoạt động với anh ta, tôi khuyên bạn nên sử dụng mô-đun mmap để kiểm soát nhiều hơn (giả sử anh ta muốn gắn bó với cách tiếp cận cơ bản này).
Jacob Gabrielson

Tôi đã đăng bản cập nhật cho ví dụ của bạn: stackoverflow.com/questions/659865/…
jfs

@JacobGabrielson: Bản sao đã được thực hiện. Câu hỏi ban đầu là về việc liệu bản sao có được tạo ra hay không.
abhinavkulkarni 19/09/13

3

Các quy trình khác nhau có không gian địa chỉ khác nhau. Giống như chạy các phiên bản khác nhau của trình thông dịch. Đó là những gì IPC (giao tiếp giữa các quy trình) là để làm.

Bạn có thể sử dụng hàng đợi hoặc đường ống cho mục đích này. Bạn cũng có thể sử dụng rpc qua tcp nếu bạn muốn phân phối các quy trình qua mạng sau này.

http://docs.python.org/dev/library/multiprocessing.html#exchacting-objects-between-processes


2
Tôi không nghĩ IPC sẽ thích hợp cho việc này; đây là dữ liệu chỉ đọc mà mọi người cần truy cập. Không có cảm giác chuyển nó xung quanh giữa các quá trình; tệ nhất là mỗi người có thể đọc bản sao của chính mình. Tôi đang cố gắng tiết kiệm bộ nhớ bằng cách không có một bản sao riêng biệt trong mỗi quá trình.
Diễu hành

Bạn có thể có một quy trình chính ủy quyền các phần dữ liệu để thực hiện cho các quy trình phụ khác. Các nô lệ có thể yêu cầu dữ liệu hoặc nó có thể đẩy dữ liệu. Bằng cách này, không phải mọi tiến trình sẽ có một bản sao của toàn bộ đối tượng.
Vasil

1
@Vasil: Điều gì sẽ xảy ra nếu mỗi quá trình cần toàn bộ tập dữ liệu và chỉ chạy một thao tác khác trên đó?
Will

1

Không liên quan trực tiếp đến đa xử lý, nhưng từ ví dụ của bạn, có vẻ như bạn chỉ có thể sử dụng mô-đun kệ hoặc thứ gì đó tương tự. "Big_lookup_object" có thực sự phải nằm hoàn toàn trong bộ nhớ không?


Điểm tốt, tôi chưa so sánh trực tiếp hiệu suất của trên đĩa với trong bộ nhớ. Tôi đã cho rằng sẽ có một sự khác biệt lớn, nhưng tôi chưa thực sự thử nghiệm.
Diễu hành

1

Không, nhưng bạn có thể tải dữ liệu của mình dưới dạng quy trình con và cho phép nó chia sẻ dữ liệu của mình với các quy trình con khác. xem bên dưới.

import time
import multiprocessing

def load_data( queue_load, n_processes )

    ... load data here into some_variable

    """
    Store multiple copies of the data into
    the data queue. There needs to be enough
    copies available for each process to access. 
    """

    for i in range(n_processes):
        queue_load.put(some_variable)


def work_with_data( queue_data, queue_load ):

    # Wait for load_data() to complete
    while queue_load.empty():
        time.sleep(1)

    some_variable = queue_load.get()

    """
    ! Tuples can also be used here
    if you have multiple data files
    you wish to keep seperate.  
    a,b = queue_load.get()
    """

    ... do some stuff, resulting in new_data

    # store it in the queue
    queue_data.put(new_data)


def start_multiprocess():

    n_processes = 5

    processes = []
    stored_data = []

    # Create two Queues
    queue_load = multiprocessing.Queue()
    queue_data = multiprocessing.Queue()

    for i in range(n_processes):

        if i == 0:
            # Your big data file will be loaded here...
            p = multiprocessing.Process(target = load_data,
            args=(queue_load, n_processes))

            processes.append(p)
            p.start()   

        # ... and then it will be used here with each process
        p = multiprocessing.Process(target = work_with_data,
        args=(queue_data, queue_load))

        processes.append(p)
        p.start()

    for i in range(n_processes)
        new_data = queue_data.get()
        stored_data.append(new_data)    

    for p in processes:
        p.join()
    print(processes)    

-4

Đối với nền tảng Linux / Unix / MacOS, forkmap là một giải pháp nhanh chóng và hiệu quả.

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.