Лучший способ дождаться очереди многопроцессорной обработки Python - PullRequest
0 голосов
/ 30 августа 2018

Я впервые серьезно играю с параллельными вычислениями. Я использую модуль multiprocessing в Python и сталкиваюсь с этой проблемой:

Потребитель очереди запускается в другом процессе, нежели производитель очереди, первый должен дождаться, пока последний завершит свою работу, прежде чем прекратить итерации по очереди. Иногда потребитель быстрее, чем производитель, и очередь остается пустой. Если я не поставлю условие, программа не остановится.

В примере кода я использую шаблон PRODUCER_IS_OVER для примера того, что мне нужно.

следующий код набросок проблемы:

def save_data(save_que, file_):
    ### Coroutine instantiation
    PRODUCER_IS_OVER = False
    empty = False
    ### Queue consumer
    while not(empty and PRODUCER_IS_OVER):
        try:
            data = save_que.get()
            print("saving data",data)
        except:
            empty = save_que.empty()
            print(empty)
            pass
        #PRODUCER_IS_OVER = get_condition()
    print ("All data saved")
    return

def get_condition():
    ###NameError: global name 'PRODUCER_IS_OVER' is not defined
    if PRODUCER_IS_OVER:
        return True
    else:
        return False


def produce_data(save_que):
    for _ in range(5):
        time.sleep(random.randint(1,5))
        data = random.randint(1,10)
        print("sending data", data)
        save_que.put(data)

### Main function here
import random
import time
from multiprocessing import Queue, Manager, Process
manager = Manager()
save_que = manager.Queue()
file_ = "file"
save_p    = Process(target= save_data, args=(save_que, file_))
save_p.start()
PRODUCER_IS_OVER = False
produce_data(save_que)
PRODUCER_IS_OVER = True
save_p.join()

produce_data занимает переменное время, и я хочу, чтобы процесс save_p запускался ПЕРЕД заполнением очереди, чтобы использовать очередь, пока она заполнена. Я думаю, что есть обходной путь, чтобы сообщить, когда прекратить итерацию, но я хочу знать, существует ли правильный способ сделать это. Я пробовал и мультипроцессорную обработку .Pipe и .Lock, но я не знаю, как реализовать правильно и эффективно.

решено: это лучший способ?

следующий код реализует STOPMESSAGE в Q, работает нормально, я могу уточнить его с помощью класса QMsg, если язык поддерживает только статические типы.

def save_data(save_que, file_):
    # Coroutine instantiation
    PRODUCER_IS_OVER = False
    empty = False
    # Queue consumer
    while not(empty and PRODUCER_IS_OVER):
        data = save_que.get()
        empty = save_que.empty()
        print("saving data", data)
        if data == "STOP":
            PRODUCER_IS_OVER = True
    print("All data saved")
    return


def get_condition():
    # NameError: global name 'PRODUCER_IS_OVER' is not defined
    if PRODUCER_IS_OVER:
        return True
    else:
        return False


def produce_data(save_que):
    for _ in range(5):
        time.sleep(random.randint(1, 5))
        data = random.randint(1, 10)
        print("sending data", data)
        save_que.put(data)
    save_que.put("STOP")


# Main function here
import random
import time
from multiprocessing import Queue, Manager, Process
manager = Manager()
save_que = manager.Queue()
file_ = "file"
save_p = Process(target=save_data, args=(save_que, file_))
save_p.start()
PRODUCER_IS_OVER = False
produce_data(save_que)
PRODUCER_IS_OVER = True
save_p.join()

Но это не может работать, если очередь создается несколькими отдельными процессами: кто будет отправлять сообщение ALT в этом случае?

другое решение - сохранить индекс процессов в списке и выполнить:

def some_alive():
    for p in processes:
        if p.is_alive():
            return True
    return False

Но multiprocessing поддерживает метод .is_alive только в родительском процессе, что является ограничением в моем случае.

спасибо

1 Ответ

0 голосов
/ 30 августа 2018

То, что вы запрашиваете, это поведение по умолчанию queue.get. Он будет ждать (блокировать), пока элемент не будет доступен из очереди. Отправка дозорного значения - действительно предпочтительный способ завершить дочерний процесс.

Ваш сценарий может быть упрощен до чего-то такого:

import random
import time
from multiprocessing import Manager, Process


def save_data(save_que, file_):
    for data in iter(save_que.get, 'STOP'):
        print("saving data", data)
    print("All data saved")
    return


def produce_data(save_que):
    for _ in range(5):
        time.sleep(random.randint(1, 5))
        data = random.randint(1, 10)
        print("sending data", data)
        save_que.put(data)
    save_que.put("STOP")


if __name__ == '__main__':

    manager = Manager()
    save_que = manager.Queue()
    file_ = "file"
    save_p = Process(target=save_data, args=(save_que, file_))
    save_p.start()
    produce_data(save_que)
    save_p.join()

Изменить, чтобы ответить на вопрос в комментарии:

Как мне реализовать сообщение остановки в случае, если к метке получают доступ несколько разных агентов, и у каждого из них есть случайное время для завершения своей задачи?

Это не так уж и отличается, вы должны поместить столько же дозорных значений в очередь, сколько у вас есть потребителей.

Служебная функция, которая возвращает streamlogger, чтобы увидеть, где находится действие:

def get_stream_logger(level=logging.DEBUG):
    """Return logger with configured StreamHandler."""
    stream_logger = logging.getLogger('stream_logger')
    stream_logger.handlers = []
    stream_logger.setLevel(level)
    sh = logging.StreamHandler()
    sh.setLevel(level)
    fmt = '[%(asctime)s %(levelname)-8s %(processName)s] --- %(message)s'
    formatter = logging.Formatter(fmt)
    sh.setFormatter(formatter)
    stream_logger.addHandler(sh)

    return stream_logger

Код с несколькими потребителями:

import random
import time
from multiprocessing import Manager, Process
import logging

def save_data(save_que, file_):
    stream_logger = get_stream_logger()
    for data in iter(save_que.get, 'STOP'):
        time.sleep(random.randint(1, 5))  # random delay
        stream_logger.debug(f"saving: {data}")  # DEBUG
    stream_logger.debug("all data saved")  # DEBUG
    return


def produce_data(save_que, n_workers):
    stream_logger = get_stream_logger()
    for _ in range(5):
        time.sleep(random.randint(1, 5))
        data = random.randint(1, 10)
        stream_logger.debug(f"producing: {data}")  # DEBUG
        save_que.put(data)

    for _ in range(n_workers):
        save_que.put("STOP")


if __name__ == '__main__':

    file_ = "file"
    n_processes = 2

    manager = Manager()
    save_que = manager.Queue()

    processes = []
    for _ in range(n_processes):
        processes.append(Process(target=save_data, args=(save_que, file_)))

    for p in processes:
        p.start()

    produce_data(save_que, n_workers=n_processes)

    for p in processes:
        p.join()

Пример вывода:

[2018-09-02 20:10:35,885 DEBUG    MainProcess] --- producing: 2
[2018-09-02 20:10:38,887 DEBUG    MainProcess] --- producing: 8
[2018-09-02 20:10:38,887 DEBUG    Process-2] --- saving: 2
[2018-09-02 20:10:39,889 DEBUG    MainProcess] --- producing: 8
[2018-09-02 20:10:40,889 DEBUG    Process-3] --- saving: 8
[2018-09-02 20:10:40,890 DEBUG    Process-2] --- saving: 8
[2018-09-02 20:10:42,890 DEBUG    MainProcess] --- producing: 1
[2018-09-02 20:10:43,891 DEBUG    Process-3] --- saving: 1
[2018-09-02 20:10:46,893 DEBUG    MainProcess] --- producing: 5
[2018-09-02 20:10:46,894 DEBUG    Process-3] --- all data saved
[2018-09-02 20:10:50,895 DEBUG    Process-2] --- saving: 5
[2018-09-02 20:10:50,896 DEBUG    Process-2] --- all data saved

Process finished with exit code 0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...