Як я повинен увійти під час використання багатопроцесорної роботи в Python?


239

Зараз у мене є центральний модуль в рамках, який породжує декілька процесів за допомогою multiprocessingмодуля Python 2.6 . Тому що воно використовує multiprocessing, є модуль рівня многопроцессорная-Aware журнал, LOG = multiprocessing.get_logger(). Згідно з документами , цей реєстратор має блокування, що ділиться процесами, так що ви не можете збирати речі sys.stderr(або будь-який файл файлових файлів), одночасно записуючи на нього кілька процесів.

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


10
Документи, на які ви посилаєтесь, констатують прямо протилежне тому, що ви говорите, у реєстратора немає загальних процесів блокування, і все змішується - проблема, яка була і у мене.
Себастьян Бласк

3
див. приклади в документах stdlib: Реєстрація в один файл з декількох процесів . Рецепти не вимагають, щоб інші модулі були обізнані в багатьох процесах.
jfs

Отже, для чого корисний випадок multiprocessing.get_logger()? Схоже, що на основі цих інших способів ведення журналу функціонування multiprocessingжурналу малоцінне.
Тім Людвинський

4
get_logger()- це реєстратор, який використовується самим multiprocessingмодулем. Це корисно, якщо ви хочете налагоджувати multiprocessingпроблему.
jfs

Відповіді:


69

Єдиний спосіб впоратися з цим не нав'язливо:

  1. Породжує кожен робочий процес таким чином, щоб його журнал переходив до іншого дескриптора файлів (на диск чи на трубу.) В ідеалі всі записи журналу повинні бути відмічені часом.
  2. Потім ваш контролер може виконати одну з наступних дій:
    • Якщо ви використовуєте файли диска: Збійте файли журналу в кінці запуску, відсортовані за часовою міткою
    • Якщо використовуються труби (рекомендується): Зробити записи журналу на ходу з усіх труб у центральний файл журналу. (Наприклад, періодично selectз дескрипторів файлів труб виконайте сортування об'єднань на доступних записах журналу та перейдіть до централізованого журналу. Повторіть.)

Приємно, це були 35-ті, перш ніж я подумав про це (думав, що використовую atexit:-). Проблема полягає в тому, що вона не дасть вам прочитати в режимі реального часу. Це може бути частиною ціни багатопроцесорної роботи на відміну від багатопотокової.
cdleary

@cdleary, використовуючи трубопровідний підхід, це було б майже в реальному часі (особливо, якщо stderr не буферується в породжених процесах.)
vladr

1
До речі, тут велике припущення: не Windows. Ви в Windows?
владр

22
Чому б не просто використати багатопроцесорну чергу та ланцюжок журналу в основному процесі замість цього? Здається, простіше.
Брендон Родос

1
@BrandonRhodes - Як я вже сказав, ненав'язливо . Використання multiprocessing.Queueне буде простішим, якщо є багато коду для перенаправлення для використання multiprocessing.Queue, та / або якщо продуктивність є проблемою
vladr

122

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

( Примітка. Це твердо закодовано RotatingFileHandler, що є моїм власним випадком використання.)


Оновлення: @javier тепер підтримує такий підхід як пакет, доступний для Pypi - див. Багатопроцесорний журнал на Pypi, github за адресою https://github.com/jruere/multiprocessing-logging


Оновлення: Впровадження!

Тепер це використовує чергу для правильного поводження з одночасністю, а також правильно відновлює помилки. Зараз я використовую це у виробництві вже кілька місяців, і поточна версія нижче працює без проблем.

from logging.handlers import RotatingFileHandler
import multiprocessing, threading, logging, sys, traceback

class MultiProcessingLog(logging.Handler):
    def __init__(self, name, mode, maxsize, rotate):
        logging.Handler.__init__(self)

        self._handler = RotatingFileHandler(name, mode, maxsize, rotate)
        self.queue = multiprocessing.Queue(-1)

        t = threading.Thread(target=self.receive)
        t.daemon = True
        t.start()

    def setFormatter(self, fmt):
        logging.Handler.setFormatter(self, fmt)
        self._handler.setFormatter(fmt)

    def receive(self):
        while True:
            try:
                record = self.queue.get()
                self._handler.emit(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except EOFError:
                break
            except:
                traceback.print_exc(file=sys.stderr)

    def send(self, s):
        self.queue.put_nowait(s)

    def _format_record(self, record):
        # ensure that exc_info and args
        # have been stringified.  Removes any chance of
        # unpickleable things inside and possibly reduces
        # message size sent over the pipe
        if record.args:
            record.msg = record.msg % record.args
            record.args = None
        if record.exc_info:
            dummy = self.format(record)
            record.exc_info = None

        return record

    def emit(self, record):
        try:
            s = self._format_record(record)
            self.send(s)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def close(self):
        self._handler.close()
        logging.Handler.close(self)

4
Вищенаведений обробник виконує всі записи файлів з батьківського процесу та використовує лише один потік для отримання повідомлень, переданих від дочірніх процесів. Якщо ви викликаєте сам обробник з нерегулярного дочірнього процесу, то він використовує його неправильно, і ви отримаєте всі ті ж проблеми, що і RotatingFileHandler. Я використовував вищезазначений код протягом багатьох років без проблем.
zzzeek

9
На жаль, такий підхід не працює в Windows. Від docs.python.org/library/multiprocessing.html 16.6.2.12 "Зауважте, що в дочірніх процесах Windows успадковується лише рівень реєстратора батьківського процесу - будь-яке інше налаштування журналу не передається у спадок." Підпроцеси не успадковуватимуть обробник, і ви не можете передати його явно, оскільки це не можна вибрати.
Ной Ітер

2
Варто зазначити, що multiprocessing.Queueвикористовується нитка для в put(). Тому не створюйте посилань put(тобто записуйте msg за допомогою MultiProcessingLogобробника) перед створенням усіх підпроцесів. Інакше нитка буде мертвою під час дочірнього процесу. Одне рішення - зателефонувати Queue._after_fork()на початку кожного дочірнього процесу або використовувати multiprocessing.queues.SimpleQueueнатомість, що не включає нитку, але блокує.
Danqi Wang

5
Чи можете ви додати простий приклад, який показує ініціалізацію, а також використання з гіпотетичного дочірнього процесу? Я не зовсім впевнений, яким чином дочірній процес повинен отримати доступ до черги, не встановлюючи інший екземпляр вашого класу.
JesseBuesking

11
@zzzeek, ​​це рішення добре, але я не зміг знайти пакет із ним чи щось подібне, тому я створив такий, що називається multiprocessing-logging.
Хав’єр

30

QueueHandlerє рідною в Python 3.2+ і робить саме це. Він легко копіюється в попередніх версіях.

Документи Python мають два повних приклади: Реєстрація в один файл з декількох процесів

Для тих, хто використовує Python <3.2, просто скопіюйте QueueHandlerсвій власний код з: https://gist.github.com/vsajip/591589 або ж імпортуйте логутили .

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


21

Нижче - ще одне рішення з акцентом на простоту для всіх інших (як я), які потрапляють сюди від Google. Ведення журналу має бути простим! Тільки для 3,2 або вище.

import multiprocessing
import logging
from logging.handlers import QueueHandler, QueueListener
import time
import random


def f(i):
    time.sleep(random.uniform(.01, .05))
    logging.info('function called with {} in worker thread.'.format(i))
    time.sleep(random.uniform(.01, .05))
    return i


def worker_init(q):
    # all records from worker processes go to qh and then into q
    qh = QueueHandler(q)
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logger.addHandler(qh)


def logger_init():
    q = multiprocessing.Queue()
    # this is the handler for all log records
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(levelname)s: %(asctime)s - %(process)s - %(message)s"))

    # ql gets records from the queue and sends them to the handler
    ql = QueueListener(q, handler)
    ql.start()

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    # add the handler to the logger so records from this process are handled
    logger.addHandler(handler)

    return ql, q


def main():
    q_listener, q = logger_init()

    logging.info('hello from main thread')
    pool = multiprocessing.Pool(4, worker_init, [q])
    for result in pool.map(f, range(10)):
        pass
    pool.close()
    pool.join()
    q_listener.stop()

if __name__ == '__main__':
    main()

2
QueueHandlerІ QueueListenerкласи можуть бути використані на Python 2.7 , а також, доступні в logutilsпакеті.
Лев Левицький

5
Журнал основного процесу також повинен використовувати QueueHandler. У вашому поточному коді основним процесом є обхід черги, щоб між основним процесом і робочими могли бути змагальні умови. Кожен повинен увійти до черги (через QueueHandler), і лише QueueListener має право до входу в StreamHandler.
Ісмаїл ЕЛ АТІФІ

Крім того, не потрібно ініціювати реєстратор у кожної дитини. Просто ініціюйте реєстратор у батьківському процесі та отримайте реєстратор у кожному дочірньому процесі.
okwap

20

Ще однією альтернативою можуть бути різні обробники журналів, що не базуються на файлах, у loggingпакеті :

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(і інші)

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

Бажання SyslogHandlerб подбати про це і для вас. Звичайно, ви можете використовувати власний екземпляр syslog, а не системний.


13

Варіант інших, що зберігає окремий потік журналу та черги.

"""sample code for logging in subprocesses using multiprocessing

* Little handler magic - The main process uses loggers and handlers as normal.
* Only a simple handler is needed in the subprocess that feeds the queue.
* Original logger name from subprocess is preserved when logged in main
  process.
* As in the other implementations, a thread reads the queue and calls the
  handlers. Except in this implementation, the thread is defined outside of a
  handler, which makes the logger definitions simpler.
* Works with multiple handlers.  If the logger in the main process defines
  multiple handlers, they will all be fed records generated by the
  subprocesses loggers.

tested with Python 2.5 and 2.6 on Linux and Windows

"""

import os
import sys
import time
import traceback
import multiprocessing, threading, logging, sys

DEFAULT_LEVEL = logging.DEBUG

formatter = logging.Formatter("%(levelname)s: %(asctime)s - %(name)s - %(process)s - %(message)s")

class SubProcessLogHandler(logging.Handler):
    """handler used by subprocesses

    It simply puts items on a Queue for the main process to log.

    """

    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue

    def emit(self, record):
        self.queue.put(record)

class LogQueueReader(threading.Thread):
    """thread to write subprocesses log records to main process log

    This thread reads the records written by subprocesses and writes them to
    the handlers defined in the main process's handlers.

    """

    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.daemon = True

    def run(self):
        """read from the queue and write to the log handlers

        The logging documentation says logging is thread safe, so there
        shouldn't be contention between normal logging (from the main
        process) and this thread.

        Note that we're using the name of the original logger.

        """
        # Thanks Mike for the error checking code.
        while True:
            try:
                record = self.queue.get()
                # get the logger for this record
                logger = logging.getLogger(record.name)
                logger.callHandlers(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except EOFError:
                break
            except:
                traceback.print_exc(file=sys.stderr)

class LoggingProcess(multiprocessing.Process):

    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def _setupLogger(self):
        # create the logger to use.
        logger = logging.getLogger('test.subprocess')
        # The only handler desired is the SubProcessLogHandler.  If any others
        # exist, remove them. In this case, on Unix and Linux the StreamHandler
        # will be inherited.

        for handler in logger.handlers:
            # just a check for my sanity
            assert not isinstance(handler, SubProcessLogHandler)
            logger.removeHandler(handler)
        # add the handler
        handler = SubProcessLogHandler(self.queue)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        # On Windows, the level will not be inherited.  Also, we could just
        # set the level to log everything here and filter it in the main
        # process handlers.  For now, just set it from the global default.
        logger.setLevel(DEFAULT_LEVEL)
        self.logger = logger

    def run(self):
        self._setupLogger()
        logger = self.logger
        # and here goes the logging
        p = multiprocessing.current_process()
        logger.info('hello from process %s with pid %s' % (p.name, p.pid))


if __name__ == '__main__':
    # queue used by the subprocess loggers
    queue = multiprocessing.Queue()
    # Just a normal logger
    logger = logging.getLogger('test')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(DEFAULT_LEVEL)
    logger.info('hello from the main process')
    # This thread will read from the subprocesses and write to the main log's
    # handlers.
    log_queue_reader = LogQueueReader(queue)
    log_queue_reader.start()
    # create the processes.
    for i in range(10):
        p = LoggingProcess(queue)
        p.start()
    # The way I read the multiprocessing warning about Queue, joining a
    # process before it has finished feeding the Queue can cause a deadlock.
    # Also, Queue.empty() is not realiable, so just make sure all processes
    # are finished.
    # active_children joins subprocesses when they're finished.
    while multiprocessing.active_children():
        time.sleep(.1)

Мені подобається ідея отримання імені реєстратора з запису черги. Це дозволяє використовувати звичайні fileConfig()в MainProcess та ледь налаштований реєстратор у PoolWorkers (лише для setLevel(logging.NOTSET)). Як я вже згадував в іншому коментарі, я використовую Pool, тому мені довелося отримати свою чергу (проксі) від менеджера замість багатопроцесорної обробки, щоб її можна було вибрати. Це дозволяє мені передавати чергу працівникові всередині словника (більшість з яких походить від аргументованого об’єкта за допомогою vars()). Я відчуваю, що врешті-решт це найкращий підхід для MS Windows, у якого бракує fork () та порушує рішення @zzzeak.
mlt

@mlt Я думаю, ви також можете поставити багатопроцесорну чергу в init замість використання диспетчера (див. відповідь на stackoverflow.com/questions/25557686/… - мова йде про Locks, але я вважаю, що це працює і для черг)
фантастичний

@fantabolous Це не буде працювати в MS Windows або будь-якій іншій платформі, яка бракує fork. Таким чином, кожен процес матиме власну незалежну марну чергу. Другий підхід у пов'язаному питанні не працює на таких платформах. Це спосіб не портативного коду.
mlt

@mlt Цікаво. Я використовую Windows, і, здається, це працює для мене нормально - недовго після того, як я востаннє прокоментував, я створив пул процесів, що надають спільний доступ multiprocessing.Queueдо основного процесу, і я постійно користуюся ним. Не буду претендувати на розуміння, чому це працює, хоча.
фантастичний

10

Усі поточні рішення занадто поєднані з конфігурацією журналу за допомогою обробника. Моє рішення має таку архітектуру та особливості:

  • Ви можете використовувати будь-яку конфігурацію журналу, яку хочете
  • Журнал проводиться в демоновій нитці
  • Безпечне відключення демона за допомогою контекстного менеджера
  • Зв’язок з потоком журналу здійснюється компанією multiprocessing.Queue
  • У підпроцесах logging.Logger(і вже визначені екземпляри) виправлені, щоб надіслати всі записи до черги
  • Нове : відстеження форматування і повідомлення перед відправленням у чергу, щоб запобігти помилкам підбору

Код із прикладом використання та виведенням можна знайти у такому Gist: https://gist.github.com/schlamar/7003737


Якщо я що - то відсутня, це не на самому ділі демон нитку, так як ви ніколи не ставили daemon_thread.daemonв True. Мені потрібно було це зробити для того, щоб моя програма Python вийшла належним чином, коли в контекстному менеджері відбувається виняток.
blah238

Мені також потрібно було виловлювати, записувати та проковтувати винятки, кинуті ціллю funcв logged_callіншому випадку, інакше виняток буде зібраний з іншим результатом журналу . Ось моя змінена версія цього: gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

Оскільки ми можемо представити багатопроцесорний журнал як багато видавців, так і один підписник (слухач), використання ZeroMQ для реалізації повідомлень PUB-SUB - це справді варіант.

Більше того, модуль PyZMQ , зв'язки Python для ZMQ, реалізує PUBHandler , який є об'єктом для публікації повідомлень журналу через zmq.PUB-сокет.

В Інтернеті є рішення для централізованого ведення журналу із розповсюдженого додатку за допомогою PyZMQ та PUBHandler, яке можна легко прийняти для локальної роботи з декількома процесами публікації.

formatters = {
    logging.DEBUG: logging.Formatter("[%(name)s] %(message)s"),
    logging.INFO: logging.Formatter("[%(name)s] %(message)s"),
    logging.WARN: logging.Formatter("[%(name)s] %(message)s"),
    logging.ERROR: logging.Formatter("[%(name)s] %(message)s"),
    logging.CRITICAL: logging.Formatter("[%(name)s] %(message)s")
}

# This one will be used by publishing processes
class PUBLogger:
    def __init__(self, host, port=config.PUBSUB_LOGGER_PORT):
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.DEBUG)
        self.ctx = zmq.Context()
        self.pub = self.ctx.socket(zmq.PUB)
        self.pub.connect('tcp://{0}:{1}'.format(socket.gethostbyname(host), port))
        self._handler = PUBHandler(self.pub)
        self._handler.formatters = formatters
        self._logger.addHandler(self._handler)

    @property
    def logger(self):
        return self._logger

# This one will be used by listener process
class SUBLogger:
    def __init__(self, ip, output_dir="", port=config.PUBSUB_LOGGER_PORT):
        self.output_dir = output_dir
        self._logger = logging.getLogger()
        self._logger.setLevel(logging.DEBUG)

        self.ctx = zmq.Context()
        self._sub = self.ctx.socket(zmq.SUB)
        self._sub.bind('tcp://*:{1}'.format(ip, port))
        self._sub.setsockopt(zmq.SUBSCRIBE, "")

        handler = handlers.RotatingFileHandler(os.path.join(output_dir, "client_debug.log"), "w", 100 * 1024 * 1024, 10)
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter("%(asctime)s;%(levelname)s - %(message)s")
        handler.setFormatter(formatter)
        self._logger.addHandler(handler)

  @property
  def sub(self):
      return self._sub

  @property
  def logger(self):
      return self._logger

#  And that's the way we actually run things:

# Listener process will forever listen on SUB socket for incoming messages
def run_sub_logger(ip, event):
    sub_logger = SUBLogger(ip)
    while not event.is_set():
        try:
            topic, message = sub_logger.sub.recv_multipart(flags=zmq.NOBLOCK)
            log_msg = getattr(logging, topic.lower())
            log_msg(message)
        except zmq.ZMQError as zmq_error:
            if zmq_error.errno == zmq.EAGAIN:
                pass


# Publisher processes loggers should be initialized as follows:

class Publisher:
    def __init__(self, stop_event, proc_id):
        self.stop_event = stop_event
        self.proc_id = proc_id
        self._logger = pub_logger.PUBLogger('127.0.0.1').logger

     def run(self):
         self._logger.info("{0} - Sending message".format(proc_id))

def run_worker(event, proc_id):
    worker = Publisher(event, proc_id)
    worker.run()

# Starting subscriber process so we won't loose publisher's messages
sub_logger_process = Process(target=run_sub_logger,
                                 args=('127.0.0.1'), stop_event,))
sub_logger_process.start()

#Starting publisher processes
for i in range(MAX_WORKERS_PER_CLIENT):
    processes.append(Process(target=run_worker,
                                 args=(stop_event, i,)))
for p in processes:
    p.start()

6

Мені також подобається відповідь zzzeek, ​​але Андре вірно, що потрібна черга, щоб запобігти гарбаті. Я мав деяку удачу з трубою, але побачив гарбуз, який дещо очікується. Реалізація виявилася важче, ніж я думав, особливо через роботу в Windows, де існують деякі додаткові обмеження щодо глобальних змінних та інших матеріалів (див.: Як реалізована багатопроцесорна програма Python у Windows? )

Але я, нарешті, спрацював. Цей приклад, мабуть, не ідеальний, тому коментарі та пропозиції вітаються. Він також не підтримує налаштування форматера чи іншого, крім кореневого реєстратора. По суті, ви повинні повторно запроваджувати реєстратор у кожному з процесів пулу з чергою та встановлювати інші атрибути на реєстраторі.

Знову ж таки, будь-які пропозиції щодо вдосконалення коду вітаються. Я, звичайно, ще не знаю всіх хитрощів Python :-)

import multiprocessing, logging, sys, re, os, StringIO, threading, time, Queue

class MultiProcessingLogHandler(logging.Handler):
    def __init__(self, handler, queue, child=False):
        logging.Handler.__init__(self)

        self._handler = handler
        self.queue = queue

        # we only want one of the loggers to be pulling from the queue.
        # If there is a way to do this without needing to be passed this
        # information, that would be great!
        if child == False:
            self.shutdown = False
            self.polltime = 1
            t = threading.Thread(target=self.receive)
            t.daemon = True
            t.start()

    def setFormatter(self, fmt):
        logging.Handler.setFormatter(self, fmt)
        self._handler.setFormatter(fmt)

    def receive(self):
        #print "receive on"
        while (self.shutdown == False) or (self.queue.empty() == False):
            # so we block for a short period of time so that we can
            # check for the shutdown cases.
            try:
                record = self.queue.get(True, self.polltime)
                self._handler.emit(record)
            except Queue.Empty, e:
                pass

    def send(self, s):
        # send just puts it in the queue for the server to retrieve
        self.queue.put(s)

    def _format_record(self, record):
        ei = record.exc_info
        if ei:
            dummy = self.format(record) # just to get traceback text into record.exc_text
            record.exc_info = None  # to avoid Unpickleable error

        return record

    def emit(self, record):
        try:
            s = self._format_record(record)
            self.send(s)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def close(self):
        time.sleep(self.polltime+1) # give some time for messages to enter the queue.
        self.shutdown = True
        time.sleep(self.polltime+1) # give some time for the server to time out and see the shutdown

    def __del__(self):
        self.close() # hopefully this aids in orderly shutdown when things are going poorly.

def f(x):
    # just a logging command...
    logging.critical('function number: ' + str(x))
    # to make some calls take longer than others, so the output is "jumbled" as real MP programs are.
    time.sleep(x % 3)

def initPool(queue, level):
    """
    This causes the logging module to be initialized with the necessary info
    in pool threads to work correctly.
    """
    logging.getLogger('').addHandler(MultiProcessingLogHandler(logging.StreamHandler(), queue, child=True))
    logging.getLogger('').setLevel(level)

if __name__ == '__main__':
    stream = StringIO.StringIO()
    logQueue = multiprocessing.Queue(100)
    handler= MultiProcessingLogHandler(logging.StreamHandler(stream), logQueue)
    logging.getLogger('').addHandler(handler)
    logging.getLogger('').setLevel(logging.DEBUG)

    logging.debug('starting main')

    # when bulding the pool on a Windows machine we also have to init the logger in all the instances with the queue and the level of logging.
    pool = multiprocessing.Pool(processes=10, initializer=initPool, initargs=[logQueue, logging.getLogger('').getEffectiveLevel()] ) # start worker processes
    pool.map(f, range(0,50))
    pool.close()

    logging.debug('done')
    logging.shutdown()
    print "stream output is:"
    print stream.getvalue()

1
Цікаво, чи if 'MainProcess' == multiprocessing.current_process().name:можна використовувати замість проходження child?
mlt

У випадку, якщо хтось інший намагається використовувати пул процесів замість окремих об'єктів процесу в Windows, варто згадати, що для передачі черги в підпроцеси використовується диспетчер , оскільки він безпосередньо не підбирається.
mlt

Ця реалізація спрацювала для мене добре. Я змінив його для роботи з довільною кількістю обробників. Таким чином ви можете налаштувати ваші кореневі обробники не багатопроцесорним способом, тоді, коли безпечно зробити чергу, передати кореневі обробники до цього, видалити їх і зробити це єдиним обробником.
Jaxor24

3

просто опублікуйте десь свій примірник реєстратора. таким чином, інші модулі та клієнти можуть використовувати ваш API, щоб отримати реєстратор без необхідності import multiprocessing.


1
Проблема в цьому полягає в тому, що багатопроцесорні реєстратори видаються без назви, тому ви не зможете легко розшифрувати потік повідомлень. Можливо, можна було б назвати їх після створення, що зробило б більш розумним подивитися.
cdleary

добре, опублікуйте по одному реєстратору для кожного модуля або, краще, експортуйте різні закриття, які використовують реєстратор з назвою модуля. справа в тому, щоб дозволити іншим модулям використовувати ваш API
Хав'єр,

Безумовно, розумний (і +1 від мене!), Але я б пропустив можливість просто import logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')з будь-якого місця та налагодив його належну роботу.
cdleary

3
Це цікаве явище, яке я бачу, коли я використовую Python, що ми настільки звикаємо робити те, що ми хочемо, в 1 або 2 простих рядках, що простий і логічний підхід на інших мовах (наприклад, публікувати багатопроцесорний реєстратор або обгортання це в аксесуарі) все ще відчуває себе тягарем. :)
Kylotan

3

Мені сподобалась відповідь zzzeek. Я б просто замінив Трубу чергою, оскільки якщо кілька потоків / процесів використовують один і той же кінець труби для генерування повідомлень журналу, вони отримають пошкодження.


У мене виникли деякі проблеми з обробником, хоча це було не те, що повідомлення були похованими, але все це перестане працювати. Я змінив Pipe на чергу, оскільки це більш доречно. Однак помилки, які я отримував, не були вирішені цим - врешті-решт, я додав спробу / за винятком методу accept () - дуже рідко спроба реєструвати винятки зазнає невдачі і закінчилася тим, що потрапила туди. Після того, як я додав спробу / виняток, він працює тижнями без проблем, і файл standarderr буде збирати близько двох помилок винятків на тиждень.
zzzeek

2

Як щодо делегування всіх журналів до іншого процесу, який читає всі записи журналу з черги?

LOG_QUEUE = multiprocessing.JoinableQueue()

class CentralLogger(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue
        self.log = logger.getLogger('some_config')
        self.log.info("Started Central Logging process")

    def run(self):
        while True:
            log_level, message = self.queue.get()
            if log_level is None:
                self.log.info("Shutting down Central Logging process")
                break
            else:
                self.log.log(log_level, message)

central_logger_process = CentralLogger(LOG_QUEUE)
central_logger_process.start()

Просто поділіться LOG_QUEUE через будь-який з багатопроцесорних механізмів або навіть успадкування, і це все добре працює!


1

У мене є рішення, подібне до ironhacker, за винятком того, що я використовую logging.exception у своєму коді і виявив, що мені потрібно відформатувати виняток перед тим, як передати його назад у чергу, оскільки відстеження не підбираються:

class QueueHandler(logging.Handler):
    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue
    def emit(self, record):
        if record.exc_info:
            # can't pass exc_info across processes so just format now
            record.exc_text = self.formatException(record.exc_info)
            record.exc_info = None
        self.queue.put(record)
    def formatException(self, ei):
        sio = cStringIO.StringIO()
        traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
        s = sio.getvalue()
        sio.close()
        if s[-1] == "\n":
            s = s[:-1]
        return s

Я знайшов повний приклад уздовж цих ліній тут .
Aryeh Leib Taurog

1

Нижче представлений клас, який можна використовувати в середовищі Windows, вимагає ActivePython. Ви також можете успадкувати інші обробники журналів (StreamHandler тощо)

class SyncronizedFileHandler(logging.FileHandler):
    MUTEX_NAME = 'logging_mutex'

    def __init__(self , *args , **kwargs):

        self.mutex = win32event.CreateMutex(None , False , self.MUTEX_NAME)
        return super(SyncronizedFileHandler , self ).__init__(*args , **kwargs)

    def emit(self, *args , **kwargs):
        try:
            win32event.WaitForSingleObject(self.mutex , win32event.INFINITE)
            ret = super(SyncronizedFileHandler , self ).emit(*args , **kwargs)
        finally:
            win32event.ReleaseMutex(self.mutex)
        return ret

Ось приклад, який демонструє використання:

import logging
import random , time , os , sys , datetime
from string import letters
import win32api , win32event
from multiprocessing import Pool

def f(i):
    time.sleep(random.randint(0,10) * 0.1)
    ch = random.choice(letters)
    logging.info( ch * 30)


def init_logging():
    '''
    initilize the loggers
    '''
    formatter = logging.Formatter("%(levelname)s - %(process)d - %(asctime)s - %(filename)s - %(lineno)d - %(message)s")
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    file_handler = SyncronizedFileHandler(sys.argv[1])
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

#must be called in the parent and in every worker process
init_logging() 

if __name__ == '__main__':
    #multiprocessing stuff
    pool = Pool(processes=10)
    imap_result = pool.imap(f , range(30))
    for i , _ in enumerate(imap_result):
        pass

Можливо, використання multiprocessing.Lock()замість Windows Mutex зробить рішення портативним.
xmedeko

1

Ось мій простий хакер / вирішення ... не найвичерпніший, але легко модифікується та простіший для читання та розуміння, я думаю, ніж будь-які інші відповіді, які я знайшов перед тим, як писати це:

import logging
import multiprocessing

class FakeLogger(object):
    def __init__(self, q):
        self.q = q
    def info(self, item):
        self.q.put('INFO - {}'.format(item))
    def debug(self, item):
        self.q.put('DEBUG - {}'.format(item))
    def critical(self, item):
        self.q.put('CRITICAL - {}'.format(item))
    def warning(self, item):
        self.q.put('WARNING - {}'.format(item))

def some_other_func_that_gets_logger_and_logs(num):
    # notice the name get's discarded
    # of course you can easily add this to your FakeLogger class
    local_logger = logging.getLogger('local')
    local_logger.info('Hey I am logging this: {} and working on it to make this {}!'.format(num, num*2))
    local_logger.debug('hmm, something may need debugging here')
    return num*2

def func_to_parallelize(data_chunk):
    # unpack our args
    the_num, logger_q = data_chunk
    # since we're now in a new process, let's monkeypatch the logging module
    logging.getLogger = lambda name=None: FakeLogger(logger_q)
    # now do the actual work that happens to log stuff too
    new_num = some_other_func_that_gets_logger_and_logs(the_num)
    return (the_num, new_num)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    m = multiprocessing.Manager()
    logger_q = m.Queue()
    # we have to pass our data to be parallel-processed
    # we also need to pass the Queue object so we can retrieve the logs
    parallelable_data = [(1, logger_q), (2, logger_q)]
    # set up a pool of processes so we can take advantage of multiple CPU cores
    pool_size = multiprocessing.cpu_count() * 2
    pool = multiprocessing.Pool(processes=pool_size, maxtasksperchild=4)
    worker_output = pool.map(func_to_parallelize, parallelable_data)
    pool.close() # no more tasks
    pool.join()  # wrap up current tasks
    # get the contents of our FakeLogger object
    while not logger_q.empty():
        print logger_q.get()
    print 'worker output contained: {}'.format(worker_output)

1

Є цей чудовий пакет

Пакет: https://pypi.python.org/pypi/multiprocessing-logging/

код: https://github.com/jruere/multiprocessing-logging

Встановити:

pip install multiprocessing-logging

Потім додайте:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
Ця бібліотека буквально ґрунтується на черговому коментарі до поточної публікації програми SO: stackoverflow.com/a/894284/1698058 .
Кріс Хант

Витоки: stackoverflow.com/a/894284/1663382 Я вдячний за приклад використання модуля, крім документації на домашній сторінці.
Liquidgenius

0

Однією з альтернатив є записати протокол взаємної обробки у відомий файл та зареєструвати atexitобробник, щоб приєднатись до тих процесів, які зчитують його назад на stderr; однак, ви не отримаєте потоку в реальному часі на вихідні повідомлення на stderr таким чином.


є підхід , ви пропонуєте нижче ідентичний той , від Вашого коментаря stackoverflow.com/questions/641420 / ...
Iruvar

0

Якщо у вас виникають тупикові місця, що виникають у комбінації замків, ниток та вилок у loggingмодулі, про це повідомляється у звіті про помилку 6721 (див. Також пов'язане питання SO ).

Існує невелика Fixup рішення розміщена тут .

Однак це просто виправить будь-які потенційні тупики logging. Це не виправить, що речі, можливо, зібрані. Дивіться інші відповіді, представлені тут.


0

Найпростіша ідея, як згадувалося:

  • Візьміть ім'я файлу та ідентифікатор процесу поточного процесу.
  • Налаштувати a [WatchedFileHandler][1]. Причини цього оброблювача детально обговорюються тут , але коротше кажучи, існують певні гірші умови перегонів з іншими обробниками лісозаготівлі. У цього найкоротшого вікна для стану гонки.
    • Виберіть шлях для збереження журналів, таких як / var / log / ...

0

Для того, кому це може знадобитися, я написав декоратор для пакету multiprocessing_logging, який додає поточну назву процесу до журналів, тож стає зрозуміло, хто що робити.

Він також запускає install_mp_handler (), тому стає непридатним запускати його перед створенням пулу.

Це дозволяє мені бачити, який працівник створює, які журнали повідомлень.

Ось план із прикладом:

import sys
import logging
from functools import wraps
import multiprocessing
import multiprocessing_logging

# Setup basic console logger as 'logger'
logger = logging.getLogger()
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(u'%(asctime)s :: %(levelname)s :: %(message)s'))
logger.setLevel(logging.DEBUG)
logger.addHandler(console_handler)


# Create a decorator for functions that are called via multiprocessing pools
def logs_mp_process_names(fn):
    class MultiProcessLogFilter(logging.Filter):
        def filter(self, record):
            try:
                process_name = multiprocessing.current_process().name
            except BaseException:
                process_name = __name__
            record.msg = f'{process_name} :: {record.msg}'
            return True

    multiprocessing_logging.install_mp_handler()
    f = MultiProcessLogFilter()

    # Wraps is needed here so apply / apply_async know the function name
    @wraps(fn)
    def wrapper(*args, **kwargs):
        logger.removeFilter(f)
        logger.addFilter(f)
        return fn(*args, **kwargs)

    return wrapper


# Create a test function and decorate it
@logs_mp_process_names
def test(argument):
    logger.info(f'test function called via: {argument}')


# You can also redefine undecored functions
def undecorated_function():
    logger.info('I am not decorated')


@logs_mp_process_names
def redecorated(*args, **kwargs):
    return undecorated_function(*args, **kwargs)


# Enjoy
if __name__ == '__main__':
    with multiprocessing.Pool() as mp_pool:
        # Also works with apply_async
        mp_pool.apply(test, ('mp pool',))
        mp_pool.apply(redecorated)
        logger.info('some main logs')
        test('main program')

-5

Моїм дітям, які десятиліттями стикаються з тим самим питанням і знайшли це питання на цьому веб-сайті, я залишаю цю відповідь.

Простота проти надмірного ускладнення. Просто використовуйте інші інструменти. Python є приголомшливим, але він не був розроблений, щоб робити якісь речі.

Наступний фрагмент демона логротату працює для мене і не надто ускладнює речі. Заплануйте, щоб він працював щогодини і

/var/log/mylogfile.log {
    size 1
    copytruncate
    create
    rotate 10
    missingok
    postrotate
        timeext=`date -d '1 hour ago' "+%Y-%m-%d_%H"`
        mv /var/log/mylogfile.log.1 /var/log/mylogfile-$timeext.log
    endscript
}

Ось як я встановив його (символьні посилання не працюють на логротат):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.