multiprocessing: Làm cách nào để chia sẻ một mệnh lệnh giữa nhiều quy trình?


113

Một chương trình tạo ra một số quy trình hoạt động trên một hàng đợi Qcó thể nối và cuối cùng có thể thao túng một từ điển chung Dđể lưu trữ kết quả. (vì vậy mỗi quy trình con có thể sử dụng Dđể lưu trữ kết quả của nó và cũng có thể xem kết quả mà các quy trình con khác đang tạo ra)

Nếu tôi in từ điển D trong một quy trình con, tôi sẽ thấy các sửa đổi đã được thực hiện trên nó (tức là trên D). Nhưng sau khi tiến trình chính tham gia Q, nếu tôi in D, đó là một mệnh đề trống!

Tôi hiểu đó là sự cố đồng bộ hóa / khóa. Ai đó có thể cho tôi biết điều gì đang xảy ra ở đây và làm cách nào để đồng bộ hóa quyền truy cập vào D không?


1
Điều này không hoạt động như mong đợi ít nhất là trên python 3.7.2 sử dụng osx 10.14.4 Dict không được đồng bộ hóa và nội dung của nó được viết lại bởi các quy trình khác. Tuy nhiên, <code> multiprocessing.Manager (). List () </code> hoạt động như mong đợi.
Andrew Druchenko

Câu trả lời:


162

Một câu trả lời chung liên quan đến việc sử dụng một Managerđối tượng. Phỏng theo tài liệu:

from multiprocessing import Process, Manager

def f(d):
    d[1] += '1'
    d['2'] += 2

if __name__ == '__main__':
    manager = Manager()

    d = manager.dict()
    d[1] = '1'
    d['2'] = 2

    p1 = Process(target=f, args=(d,))
    p2 = Process(target=f, args=(d,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

    print d

Đầu ra:

$ python mul.py 
{1: '111', '2': 6}

4
Cảm ơn người gửi. Thật vậy, D = multiprocessing.Manager (). Dict () giải quyết vấn đề của tôi. Tôi đã sử dụng D = dict ().
dop

3
@LorenzoBelli, nếu bạn đang hỏi liệu quyền truy cập vào trình quản lý có được đồng bộ hóa hay không, tôi tin rằng câu trả lời là có. multiprocessing.Manager()trả về một ví dụ củaSyncManager , tên của nó gợi ý càng nhiều!
gửi

@senderle Tôi muốn chia sẻ trạng thái ngẫu nhiên không đáng kể của một quy trình mẹ với một quy trình con. Tôi đã thử sử dụng Managernhưng vẫn không may mắn. Bạn có thể vui lòng xem câu hỏi của tôi ở đây và xem liệu bạn có thể đưa ra giải pháp không? Tôi vẫn có thể nhận các số ngẫu nhiên khác nhau nếu tôi thực hiện np.random.seed(None)mỗi khi tôi tạo một số ngẫu nhiên, nhưng điều này không cho phép tôi sử dụng trạng thái ngẫu nhiên của quy trình mẹ, điều này không phải là điều tôi muốn. Bất kỳ trợ giúp nào cũng được đánh giá rất cao.
Amir

1
@RadioControlled rất vui khi viết một bản cập nhật, nhưng ngắn gọn, mặc dù tôi không nghĩ rằng bạn có thể trực tiếp thực hiện điều này, nhưng bạn có thể dễ dàng tạo một mệnh lệnh được quản lý mới với các khóa và giá trị tương tự và sử dụng nó thay vì bản gốc. Điều đó có đủ cho trường hợp của bạn không?
gửi

1
@senderle, đó là những gì tôi đã kết thúc. Vì vậy, câu trả lời sẽ là bạn phải làm điều đó.
Đài kiểm soát

25

đa xử lý không giống như phân luồng. Mỗi tiến trình con sẽ nhận được một bản sao của bộ nhớ của tiến trình chính. Nói chung trạng thái được chia sẻ thông qua giao tiếp (đường ống / ổ cắm), tín hiệu hoặc bộ nhớ được chia sẻ.

Đa xử lý cung cấp một số thông tin trừu tượng cho trường hợp sử dụng của bạn - trạng thái được chia sẻ được coi là cục bộ bằng cách sử dụng proxy hoặc bộ nhớ dùng chung: http://docs.python.org/library/multiprocessing.html#sharing-state-between-processes

Các phần có liên quan:


1
Cảm ơn rất nhiều. Bạn đã dẫn tôi đến giải pháp / a: multiprocessing.Manager (). Dict ().
dop

Ai đó có thể giải thích chi tiết về tuyên bố "Mỗi tiến trình con sẽ nhận được một bản sao bộ nhớ của tiến trình chính" nghĩa là gì.
Itsme2003

@ Itsme2003 theo mặc định, một tiến trình được tạo ra không có quyền truy cập vào bộ nhớ của tiến trình mẹ (đây là một trong những điểm khác biệt chính đối với các luồng). Vì vậy, khi một tiến trình cần một đối tượng của tiến trình mẹ, nó phải tạo một bản sao của nó (thay vì nhận tham chiếu đến đối tượng thực). Câu trả lời ở trên giải thích chi tiết về cách chia sẻ các đối tượng giữa các tiến trình.
Niklas Mertsch

Bởi vì điều này thường bị nhầm lẫn: Miễn là bạn không sửa đổi đối tượng, ít nhất là trong thiết lập Linux thông thường, đối tượng sẽ chỉ thực sự được lưu trữ một lần trong bộ nhớ. Nó sẽ được sao chép ngay sau khi nó được thay đổi. Điều này có thể rất quan trọng nếu bạn cần tiết kiệm bộ nhớ và không sửa đổi đối tượng.
Đài kiểm soát

16

Tôi muốn chia sẻ công việc của riêng tôi nhanh hơn lệnh của Trình quản lý, đơn giản và ổn định hơn thư viện pyshmht sử dụng nhiều bộ nhớ và không hoạt động cho Mac OS. Mặc dù mệnh lệnh của tôi chỉ hoạt động với các chuỗi đơn giản và hiện tại là bất biến. Tôi sử dụng triển khai thăm dò tuyến tính và lưu trữ các cặp khóa và giá trị trong một khối bộ nhớ riêng biệt sau bảng.

from mmap import mmap
import struct
from timeit import default_timer
from multiprocessing import Manager
from pyshmht import HashTable


class shared_immutable_dict:
    def __init__(self, a):
        self.hs = 1 << (len(a) * 3).bit_length()
        kvp = self.hs * 4
        ht = [0xffffffff] * self.hs
        kvl = []
        for k, v in a.iteritems():
            h = self.hash(k)
            while ht[h] != 0xffffffff:
                h = (h + 1) & (self.hs - 1)
            ht[h] = kvp
            kvp += self.kvlen(k) + self.kvlen(v)
            kvl.append(k)
            kvl.append(v)

        self.m = mmap(-1, kvp)
        for p in ht:
            self.m.write(uint_format.pack(p))
        for x in kvl:
            if len(x) <= 0x7f:
                self.m.write_byte(chr(len(x)))
            else:
                self.m.write(uint_format.pack(0x80000000 + len(x)))
            self.m.write(x)

    def hash(self, k):
        h = hash(k)
        h = (h + (h >> 3) + (h >> 13) + (h >> 23)) * 1749375391 & (self.hs - 1)
        return h

    def get(self, k, d=None):
        h = self.hash(k)
        while True:
            x = uint_format.unpack(self.m[h * 4:h * 4 + 4])[0]
            if x == 0xffffffff:
                return d
            self.m.seek(x)
            if k == self.read_kv():
                return self.read_kv()
            h = (h + 1) & (self.hs - 1)

    def read_kv(self):
        sz = ord(self.m.read_byte())
        if sz & 0x80:
            sz = uint_format.unpack(chr(sz) + self.m.read(3))[0] - 0x80000000
        return self.m.read(sz)

    def kvlen(self, k):
        return len(k) + (1 if len(k) <= 0x7f else 4)

    def __contains__(self, k):
        return self.get(k, None) is not None

    def close(self):
        self.m.close()

uint_format = struct.Struct('>I')


def uget(a, k, d=None):
    return to_unicode(a.get(to_str(k), d))


def uin(a, k):
    return to_str(k) in a


def to_unicode(s):
    return s.decode('utf-8') if isinstance(s, str) else s


def to_str(s):
    return s.encode('utf-8') if isinstance(s, unicode) else s


def mmap_test():
    n = 1000000
    d = shared_immutable_dict({str(i * 2): '1' for i in xrange(n)})
    start_time = default_timer()
    for i in xrange(n):
        if bool(d.get(str(i))) != (i % 2 == 0):
            raise Exception(i)
    print 'mmap speed: %d gets per sec' % (n / (default_timer() - start_time))


def manager_test():
    n = 100000
    d = Manager().dict({str(i * 2): '1' for i in xrange(n)})
    start_time = default_timer()
    for i in xrange(n):
        if bool(d.get(str(i))) != (i % 2 == 0):
            raise Exception(i)
    print 'manager speed: %d gets per sec' % (n / (default_timer() - start_time))


def shm_test():
    n = 1000000
    d = HashTable('tmp', n)
    d.update({str(i * 2): '1' for i in xrange(n)})
    start_time = default_timer()
    for i in xrange(n):
        if bool(d.get(str(i))) != (i % 2 == 0):
            raise Exception(i)
    print 'shm speed: %d gets per sec' % (n / (default_timer() - start_time))


if __name__ == '__main__':
    mmap_test()
    manager_test()
    shm_test()

Kết quả hiệu suất trên máy tính xách tay của tôi là:

mmap speed: 247288 gets per sec
manager speed: 33792 gets per sec
shm speed: 691332 gets per sec

ví dụ sử dụng đơn giản:

ht = shared_immutable_dict({'a': '1', 'b': '2'})
print ht.get('a')

14
Github? Tài liệu? làm thế nào chúng ta có thể sử dụng công cụ này?
Pavlos Panteliadis,

10

Ngoài @ senderle's ở đây, một số người cũng có thể thắc mắc cách sử dụng chức năng của multiprocessing.Pool.

Điều thú vị là có một .Pool()phương thức đối với managerphiên bản bắt chước tất cả các API quen thuộc của cấp cao nhất multiprocessing.

from itertools import repeat
import multiprocessing as mp
import os
import pprint

def f(d: dict) -> None:
    pid = os.getpid()
    d[pid] = "Hi, I was written by process %d" % pid

if __name__ == '__main__':
    with mp.Manager() as manager:
        d = manager.dict()
        with manager.Pool() as pool:
            pool.map(f, repeat(d, 10))
        # `d` is a DictProxy object that can be converted to dict
        pprint.pprint(dict(d))

Đầu ra:

$ python3 mul.py 
{22562: 'Hi, I was written by process 22562',
 22563: 'Hi, I was written by process 22563',
 22564: 'Hi, I was written by process 22564',
 22565: 'Hi, I was written by process 22565',
 22566: 'Hi, I was written by process 22566',
 22567: 'Hi, I was written by process 22567',
 22568: 'Hi, I was written by process 22568',
 22569: 'Hi, I was written by process 22569',
 22570: 'Hi, I was written by process 22570',
 22571: 'Hi, I was written by process 22571'}

Đây là một ví dụ hơi khác trong đó mỗi quy trình chỉ ghi lại ID quy trình của nó vào DictProxyđối tượng toàn cục d.


3

Có lẽ bạn có thể thử pyshmht , phần mở rộng bảng băm dựa trên bộ nhớ chia sẻ cho Python.

Để ý

  1. Nó không được kiểm tra đầy đủ, chỉ để bạn tham khảo.

  2. Nó hiện thiếu các cơ chế khóa / sem cho đa xử lý.

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.