Подтвердить что ты не робот

Поддерживает ли протоколы python многопроцессорность?

Мне сказали, что запись не может использоваться в Multiprocessing. Вы должны сделать элемент управления concurrency в случае, если многопроцессорность испортила журнал.

Но я сделал несколько тестов, кажется, что нет проблем с использованием протоколирования многопроцессорности

import time
import logging
from multiprocessing import Process, current_process, pool


# setup log
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/tmp/test.log',
                    filemode='w')


def func(the_time, logger):
    proc = current_process()
    while True:
        if time.time() >= the_time:
            logger.info('proc name %s id %s' % (proc.name, proc.pid))
            return



if __name__ == '__main__':

    the_time = time.time() + 5

    for x in xrange(1, 10):
        proc = Process(target=func, name=x, args=(the_time, logger))
        proc.start()

Как вы можете видеть из кода.

Я намеренно позволяю записывать журнал подпроцесса в тот же момент (5 с после старта), чтобы увеличить вероятность конфликта. Но конфликта вообще нет.

Итак, мой вопрос: можем ли мы использовать протоколирование в многопроцессорности? Почему так много сообщений говорят, что мы не можем?

4b9b3361

Ответ 1

Как правильно объяснил Matino: регистрация в многопроцессорной установке небезопасна, поскольку несколько процессов (которые теперь знают что-либо о других существующих) записывают в один и тот же файл, потенциально вмешиваясь друг с другом.

Теперь случается, что каждый процесс содержит дескриптор открытого файла и выполняет "добавление записи" в этот файл. Вопрос заключается в том, при каких обстоятельствах запись append является "атомарной" (то есть не может быть прервана, например, другим процессом, записывающим в тот же файл и перемежающим его вывод). Эта проблема применима к каждому языку программирования, так как в конце они будут использовать syscall для ядра. Этот ответ отвечает, при каких обстоятельствах общий файл журнала в порядке.

Это сводится к проверке размера буфера вашего буфера, на linux, который определен в /usr/include/linux/limits.h и составляет 4096 байт. Для других ОС вы найдете здесь хороший список.

Это означает: если ваша строка журнала меньше 4'096 байт (если в Linux), то приложение безопасно, если диск подключен напрямую (т.е. нет сети между ними). Но для более подробной информации, пожалуйста, проверьте первую ссылку в моем ответе. Чтобы проверить это, вы можете сделать logger.info('proc name %s id %s %s' % (proc.name, proc.pid, str(proc.name)*5000)) с разными значениями длины. Например, с 5000 я уже перепутал строки журнала в /tmp/test.log.

В этом вопросе уже существует немало решений, поэтому я не буду добавлять свое решение здесь.

Обновление: Колба и многопроцессорная обработка

Веб-фреймворки, такие как фляжка, будут выполняться несколькими сотрудниками, если они будут размещены с помощью uwsgi или nginx. В этом случае несколько процессов могут записываться в один файл журнала

Обработка ошибок в колбе выполняется через stdout/stderr, который затем обрабатывается веб-сервером (uwsgi, nginx и т.д.), который должен заботиться о том, чтобы журналы записывались корректно (см., например, этот фляж + пример nginx, возможно, также добавление информации о процессе, чтобы вы могли связать строки ошибок с процессами. колбы doc:

По умолчанию с Flask 0.11 ошибки записываются в журнал ваших веб-серверов автоматически. Однако предупреждений нет.

Таким образом, у вас все еще будет проблема с перемешанными файлами журнала, если вы используете warn, и сообщение превышает размер буфера канала.

Ответ 2

Нельзя записывать в один файл из нескольких процессов.

Согласно https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes

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

Одним из возможных решений было бы, чтобы каждый процесс записывал в свой собственный файл. Вы можете добиться этого, написав собственный обработчик, который добавляет процесс pid в конец файла:

import logging.handlers
import os


class PIDFileHandler(logging.handlers.WatchedFileHandler):

    def __init__(self, filename, mode='a', encoding=None, delay=0):
        filename = self._append_pid_to_filename(filename)
        super(PIDFileHandler, self).__init__(filename, mode, encoding, delay)

    def _append_pid_to_filename(self, filename):
        pid = os.getpid()
        path, extension = os.path.splitext(filename)
        return '{0}-{1}{2}'.format(path, pid, extension)

Тогда вам просто нужно вызвать addHandler:

logger = logging.getLogger('foo')
fh = PIDFileHandler('bar.log')
logger.addHandler(fh)

Ответ 3

Использовать очередь для правильной обработки concurrency, одновременно восстанавливая ошибки, подавая все в родительский процесс через канал.

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)

Обработчик выполняет все записи файла из родительского процесса и использует только один поток для приема сообщений, переданных из дочерних процессов