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

Ответ

Способы обмена данными зависят от того, используются ли процессы (multiprocessing) или потоки (threading), так как у них разные модели работы с памятью.

Между процессами (multiprocessing)

Процессы имеют изолированное адресное пространство памяти, поэтому для обмена данными требуются механизмы межпроцессного взаимодействия (IPC):

  1. Очереди (multiprocessing.Queue). Потоко- и процессобезопасный способ для обмена объектами. Идеально подходит для паттерна "производитель-потребитель".

    from multiprocessing import Process, Queue
    
    def worker(q):
        q.put('some data')
    
    q = Queue()
    p = Process(target=worker, args=(q,))
    p.start()
    print(q.get()) # Выведет 'some data'
    p.join()
  2. Каналы (multiprocessing.Pipe). Создает двустороннее соединение между двумя процессами. Быстрее, чем Queue, но предназначен только для двух конечных точек.

    from multiprocessing import Process, Pipe
    
    def worker(conn):
        conn.send([42, None, 'hello'])
        conn.close()
    
    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, shared_memory). Наиболее быстрый способ для обмена простыми типами данных (числа, массивы) без сериализации. Требует ручной синхронизации с помощью блокировок (Lock).

    from multiprocessing import Process, Value
    
    def increment(n):
        n.value += 1
    
    num = Value('i', 0) # 'i' - integer
    p = Process(target=increment, args=(num,))
    p.start()
    p.join()
    print(num.value) # Выведет 1

Между потоками (threading)

Потоки работают в общем адресном пространстве, поэтому могут напрямую обращаться к общим переменным. Однако такой доступ небезопасен и требует синхронизации.

  1. Очереди (queue.Queue). Это предпочтительный и самый безопасный способ обмена данными между потоками. Он инкапсулирует всю необходимую логику блокировок.

  2. Общие переменные с примитивами синхронизации. Можно использовать общие переменные, но их чтение и запись должны быть защищены с помощью threading.Lock, RLock, Semaphore и т.д., чтобы избежать состояния гонки.