может ли скрипт Python узнать, что запущен другой экземпляр того же скрипта ... и затем поговорить с ним? - PullRequest
11 голосов
/ 29 мая 2010

Я бы хотел предотвратить одновременное выполнение нескольких экземпляров одного и того же скрипта командной строки python, и я хотел бы, чтобы новый экземпляр мог отправлять данные в исходный экземпляр перед новым Экземпляр совершает самоубийство. Как я могу сделать это кроссплатформенным способом?

В частности, я хотел бы включить следующее поведение:

  1. «foo.py» запускается из командной строки и будет работать в течение длительного времени - дней или недель, пока машина не будет перезагружена или родительский процесс не убьет ее.
  2. каждые несколько минут один и тот же скрипт запускается снова, но с другими параметрами командной строки
  3. при запуске скрипт должен увидеть, запущены ли другие экземпляры.
  4. если запущены другие экземпляры, то экземпляр № 2 должен отправить свои параметры командной строки в экземпляр № 1, а затем экземпляр № 2 должен выйти.
  5. экземпляр # 1, если он получает параметры командной строки от другого скрипта, должен запустить новый поток и (используя параметры командной строки, отправленные на шаге выше) начать выполнение работы, которую собирался выполнить экземпляр # 2 .

Итак, я ищу две вещи: как программа на python может узнать, что запущен другой экземпляр, и как одна программа командной строки python может взаимодействовать с другой?

Ситуация усложняется тем, что один и тот же сценарий должен работать как в Windows, так и в Linux, поэтому в идеале решение должно использовать только стандартную библиотеку Python, а не какие-либо вызовы, специфичные для ОС. Хотя, если мне нужно иметь кодовый путь Windows и кодовый путь * nix (и большой оператор if в моем коде, чтобы выбрать один или другой), то все в порядке, если решение с "тем же кодом" невозможно.

Я понимаю, что, возможно, мог бы выработать подход на основе файлов (например, экземпляр # 1 просматривает каталог на предмет изменений, и каждый экземпляр сбрасывает файл в этот каталог, когда он хочет выполнить работу), но меня немного беспокоит очистка эти файлы после неосторожного выключения компьютера. В идеале я бы мог использовать решение в памяти. Но, опять же, я гибок, если подход на основе постоянных файлов - единственный способ сделать это, я открыт для этого варианта.

Подробнее: я пытаюсь сделать это, потому что наши серверы используют инструмент мониторинга, который поддерживает запуск сценариев Python для сбора данных мониторинга (например, результатов запроса к базе данных или вызова веб-службы), который затем инструмент индексации индексирует для дальнейшего использования. использовать. Некоторые из этих сценариев очень дороги для запуска, но дешевы для запуска после запуска (например, установление соединения с БД или выполнение запроса). Поэтому мы решили держать их в бесконечном цикле, пока родительский процесс не убьет их.

Это прекрасно работает, но на больших серверах может работать 100 экземпляров одного и того же скрипта, даже если они собирают данные каждые 20 минут каждый. Это наносит ущерб оперативной памяти, ограничениям подключения к БД и т. Д. Мы хотим переключиться с 100 процессов с 1 потоком на один процесс с 100 потоками, каждый из которых выполняет работу, которую ранее выполнял один сценарий.

Но изменить способ вызова сценариев инструментом мониторинга невозможно. Нам нужно сохранить вызов одинаковым (запустить процесс с другими параметрами командной строки), но изменить сценарии так, чтобы они распознавали, что другой активен, и чтобы «новый» сценарий отправлял свои рабочие инструкции (из параметров командной строки) поверх на «старый» сценарий.

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

Ответы [ 4 ]

10 голосов
/ 29 мая 2010

Подход Алекса Мартелли по настройке канала связи является подходящим. Я бы использовал multiprocessing.connection.Listener для создания слушателя по вашему выбору. Документация по адресу: http://docs.python.org/library/multiprocessing.html#multiprocessing-listeners-clients

Вместо того, чтобы использовать AF_INET (сокеты), вы можете использовать AF_UNIX для Linux и AF_PIPE для Windows. Надеюсь, небольшое «если» не повредит.

Редактировать : Я думаю, что пример не повредит. Хотя это базовый.

#!/usr/bin/env python

from multiprocessing.connection import Listener, Client
import socket
from array import array
from sys import argv

def myloop(address):
    try:
        listener = Listener(*address)
        conn = listener.accept()
        serve(conn)
    except socket.error, e:
        conn = Client(*address)
        conn.send('this is a client')
        conn.send('close')

def serve(conn):
    while True:
        msg = conn.recv()
        if msg.upper() == 'CLOSE':
            break
        print msg
    conn.close()

if __name__ == '__main__':
    address = ('/tmp/testipc', 'AF_UNIX')
    myloop(address)

Это работает на OS X, поэтому требует тестирования как с Linux, так и (после замены правильного адреса) Windows. С точки зрения безопасности существует множество предостережений, главным из которых является то, что conn.recv распаковывает свои данные, поэтому вам почти всегда лучше использовать recv_bytes.

9 голосов
/ 29 мая 2010

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

Ваши требования к кроссплатформенным функциям настоятельно указывают на использование сокета в качестве рассматриваемого канала связи: вы можете назначить «хорошо известный порт», который зарезервирован для вашего сценария, например, 12345, и открыть сокет на этом порту, слушая только локальный (127.0.0.1). Если попытка открыть этот сокет не удалась, поскольку рассматриваемый порт «занят», вы можете вместо этого подключиться к этому номеру порта, и это позволит вам связаться с существующим сценарием.

Если вы не знакомы с программированием сокетов, здесь есть хороший HOWTO документ . Вы также можете посмотреть соответствующую главу в Python в двух словах (я, конечно, склонен к этому; -).

1 голос
/ 29 мая 2010

Возможно, попробуйте использовать сокеты для связи?

0 голосов
/ 29 мая 2010

Похоже, ваша лучшая ставка заключается в том, чтобы придерживаться файла pid, но он должен не только содержать идентификатор процесса - он также должен включать номер порта, который прослушивает предыдущий экземпляр. Поэтому при запуске проверьте файл pid и, если он есть, посмотрите, запущен ли процесс с этим идентификатором, - если это так, отправьте ему свои данные и выйдите, в противном случае перезапишите файл pid с информацией о текущем процессе.

...