багатопроцесорна робота: Як я можу ділитись між декількома процесами?


113

Програма, яка створює декілька процесів, які працюють на черзі, що поєднується Q, і може врешті-решт маніпулювати глобальним словником Dдля зберігання результатів. (тому кожен дочірній процес може використовуватись Dдля збереження результатів, а також для того, щоб побачити, які результати дають інші дочірні процеси)

Якщо я друкую словник D у дочірньому процесі, я бачу зміни, які були зроблені на ньому (тобто на D). Але після того, як основний процес приєднається до Q, якщо я надрукую D, це порожній дикт!

Я розумію, це проблема синхронізації / блокування. Чи може хтось сказати мені, що тут відбувається, і як я можу синхронізувати доступ до D?


1
Це не працює, як очікувалося, принаймні на python 3.7.2, використовуючи osx 10.14.4 Dict не синхронізується, а його вміст переписується іншими процесами. Однак <code> multiprocessing.Manager (). List () </code> працює як очікувалося.
Андрій Друченко

Відповіді:


162

Загальна відповідь передбачає використання Managerоб’єкта. Адаптовано з документів:

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

Вихід:

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

4
Дякую senderle Дійсно, D = багатопроцесорна робота.Manager (). Dict () вирішує мою проблему. Я використовував D = dict ().
доп

3
@LorenzoBelli, якщо ви запитуєте, чи синхронізований доступ до менеджера, я вважаю, що відповідь "так". multiprocessing.Manager()повертає екземплярSyncManager , ім'я якого підказує стільки ж!
senderle

@senderle Я хочу поділитися нумером випадкового стану батьківського процесу з дочірнім процесом. Я спробував використовувати, Managerале все одно не пощастило. Чи можете ви, будь ласка, поглянути на моє запитання тут і побачити, чи можете ви запропонувати рішення? Я все ще можу отримати різні випадкові числа, якщо я роблю np.random.seed(None)щоразу, коли генерую випадкове число, але це не дозволяє мені використовувати випадковий стан батьківського процесу, який не є тим, що я хочу. Будь-яка допомога дуже вдячна.
Амір

1
@RadioControll із задоволенням пише оновлення, але коротко, хоча я не думаю, що ви можете це зробити безпосередньо, ви можете легко створити новий керований диктант з тими ж ключами та значеннями і використовувати це замість оригіналу. Це адекватно для вашої справи?
senderle

1
@senderle, це я і закінчила. Тож відповідь буде, що вам доведеться робити саме це.
Радіокерований

25

багатопроцесорна робота не схожа на різьблення. Кожен дочірній процес отримає копію пам'яті основного процесу. Зазвичай стан передається за допомогою зв'язку (труби / розетки), сигналів або спільної пам'яті.

Багатопроцесорна робота надає деякі абстракції для вашого випадку використання - спільний стан, який трактується як локальний за допомогою проксі-серверів або спільної пам'яті: http://docs.python.org/library/multiprocessing.html#sharing-state-between-proces

Відповідні розділи:


1
Дуже дякую. Ви привели мене до / рішення: multiprocessing.Manager (). Dict ().
доп

Може хтось детальніше розбереться про те, що означає висловлювання "Кожен дочірній процес отримає копію пам'яті основного процесу".
Itme2003

@ Itsme2003 за замовчуванням породжений процес не має доступу до пам'яті батьківського процесу (це одна з ключових відмінностей у потоках). Отже, коли процес потребує об'єкта батьківського процесу, він повинен створити його копію (замість отримання посилання на фактичний об'єкт). Відповідь вище пояснює, як розподілити об'єкти між процесами.
Ніклас Мертч

Оскільки це часто помиляється: Поки ви не модифікуєте об'єкт, принаймні у звичайній установці Linux, об’єкт фактично зберігатиметься лише один раз у пам'яті. Вона буде скопійована, як тільки вона буде змінена. Це може бути дуже важливим, якщо вам потрібно зберегти пам'ять і не змінювати об'єкт.
Радіокерований

16

Я хотів би поділитися своєю власною роботою, яка швидше, ніж диктант менеджера, і простішою та стабільнішою, ніж бібліотека pyshmht, яка використовує багато пам'яті та не працює для Mac OS. Хоча мій дикт працює лише для простих струн і є незмінним на даний момент. Я використовую лінійну реалізацію зондування і зберігаю пари ключів і пар значень в окремому блоці пам'яті після таблиці.

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()

На моєму ноутбуці результати роботи:

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

простий приклад використання:

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

14
Гітуб? Документація? як ми можемо використовувати цей інструмент?
Павлос Пантеліядіс

10

Окрім @ senderle тут, деякі можуть також задатися питанням, як використовувати функціонал multiprocessing.Pool.

Приємно те, що існує .Pool()метод для managerекземпляра, який імітує всі знайомі API верхнього рівня 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))

Вихід:

$ 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'}

Це дещо інший приклад, коли кожен процес просто записує свій ідентифікатор процесу до глобального DictProxyоб'єкта d.


3

Можливо, ви можете спробувати pyshmht , обмін розширенням хеш-таблиці на основі пам'яті для Python.

Зауважте

  1. Це не повністю перевірено, лише для ознайомлення.

  2. На даний момент не вистачає механізмів блокування / напівпрограми для багатопроцесорної обробки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.