Какие механизмы предоставляет модуль multiprocessing в Python для обмена данными между процессами?

Ответ

Модуль multiprocessing в Python предлагает несколько механизмов для безопасного обмена данными между процессами (IPC - Inter-Process Communication), так как процессы по умолчанию не разделяют память и работают в изолированных адресных пространствах.

  1. Очереди (multiprocessing.Queue)

    • Описание: Потокобезопасная очередь, работающая по принципу FIFO (First-In, First-Out). Объекты перед помещением в очередь сериализуются (pickle).
    • Сценарий: Идеально подходит для организации обмена сообщениями между несколькими процессами-производителями (producers) и процессами-потребителями (consumers).
      
      from multiprocessing import Process, Queue

    def worker(q): q.put('Hello from child process')

    if name == 'main': q = Queue() p = Process(target=worker, args=(q,)) p.start() print(q.get()) # Вывод: Hello from child process p.join()

  2. Каналы (multiprocessing.Pipe)

    • Описание: Создает пару соединенных объектов Connection, представляющих два конца канала. По умолчанию канал является двунаправленным.
    • Сценарий: Эффективен для двусторонней связи между двумя конкретными процессами.
      
      from multiprocessing import Process, Pipe

    def worker(conn): conn.send([42, None, 'hello']) conn.close()

    if name == 'main': parent_conn, child_conn = Pipe() p = Process(target=worker, args=(child_conn,)) p.start() print(parent_conn.recv()) # Вывод: [42, None, 'hello'] p.join()

  3. Разделяемая память (Value и Array)

    • Описание: Позволяет нескольким процессам совместно использовать и изменять одни и те же данные в памяти. Value для одного значения, Array для массива простых типов (числа, символы).
    • Сценарий: Самый быстрый способ обмена, так как избегает накладных расходов на сериализацию. Требует использования блокировок (Lock) для предотвращения состояния гонки при одновременной записи.
      
      from multiprocessing import Process, Value, Array, Lock

    def worker(n, a, lock): with lock: n.value += 1 for i in range(len(a)): a[i] = -a[i]

    if name == 'main': lock = Lock() num = Value('d', 0.0) arr = Array('i', range(10)) p = Process(target=worker, args=(num, arr, lock)) p.start() p.join() print(num.value) # Вывод: 1.0 print(list(arr)) # Вывод: [0, -1, -2, ...]

Механизм Основной сценарий Производительность Сложность использования
Queue Обмен сообщениями (многие-ко-многим) Средняя Низкая
Pipe Двусторонняя связь (один-к-одному) Высокая Низкая
Value/Array Общий доступ к простым данным Очень высокая Средняя (нужны блокировки)

Ответ 18+ 🔞

Так, слушай сюда, про эти ваши процессы в питоне. Ну, типа, каждый процесс — это как отдельная квартира, понимаешь? Они друг про друга нихуя не знают и память у них разная. А нам надо, чтобы они общались, эти распиздяи. Ну, мультипроцессинг нам три штуки подкидывает, на выбор, как в столовке.

Первое — это очередь (Queue). Представь себе конвейер на заводе, куда один кидает детали, а другой снимает. FIFO, блядь, кто первый встал — того и тапки. Всё, что туда суёшь, оно сериализуется (это типа в архив запаковывается), потом распаковывается. Идеально, когда процессов дохуя и все друг другу что-то пишут.

from multiprocessing import Process, Queue

def работяга(q):
    q.put('Привет из дочернего процесса, ёпта')

if __name__ == '__main__':
    q = Queue()
    p = Process(target=работяга, args=(q,))
    p.start()
    print(q.get())  # Выведет: Привет из дочернего процесса, ёпта
    p.join()

Второе — каналы (Pipe). Это типа проводок от двух раций. Создаёшь парочку, один конец — себе, другой — соседу. И вы можете друг другу в них орать (точнее, данные слать). По умолчанию — двусторонние, можно и туда, и обратно.

from multiprocessing import Process, Pipe

def работяга(conn):
    conn.send([42, None, 'hello'])
    conn.close()

if __name__ == '__main__':
    родитель_коннект, детский_коннект = Pipe()
    p = Process(target=работяга, args=(детский_коннект,))
    p.start()
    print(родитель_коннект.recv())  # Выведет: [42, None, 'hello']
    p.join()

Ну и третье, самое быстрое, но и самое опасное — разделяемая память (Value и Array). Это типа общий холодильник в коммуналке. Все процессы могут туда лезть и менять что хотят. Value — для одной переменной (число, строка), Array — для массива простых типов. Но тут, блядь, без блокировок (Lock) — пипец, состояние гонки обеспечено. Один пишет, другой в это же время стирает — и всё, каша.

from multiprocessing import Process, Value, Array, Lock

def работяга(n, a, lock):
    with lock:
        n.value += 1
        for i in range(len(a)):
            a[i] = -a[i]

if __name__ == '__main__':
    lock = Lock()
    num = Value('d', 0.0)
    arr = Array('i', range(10))
    p = Process(target=работяга, args=(num, arr, lock))
    p.start()
    p.join()
    print(num.value)      # Выведет: 1.0
    print(list(arr))      # Выведет: [0, -1, -2, ...]

Короче, табличка для тех, у кого мозг уже кипит:

Штука Для чего годится Скорость Сложность
Queue Много процессов, обмен сообщениями Средняя Низкая (просто кидай и бери)
Pipe Два процесса, быстрый диалог Высокая Низкая
Value/Array Общая память, скорость критична Очень высокая Средняя (следи за блокировками!)

Выбирай по обстановке, а то так и до deadlock'а недалеко, ёпта.