Python: один экземпляр программы



есть ли Питонический способ иметь только один экземпляр запущенной программы?



единственное разумное решение, которое я придумал, пытается запустить его как сервер на каком - то Порту, а затем вторая программа пытается привязаться к тому же порту-не удается. Но это не очень хорошая идея, может быть, есть что-то более легкое, чем это?



(примите во внимание, что иногда ожидается сбой программы, т. е. segfault-поэтому такие вещи, как" lock file", не будут работа)



обновление: предлагаемые решения намного сложнее и менее надежны, чем просто наличие порта, занятого несуществующим сервером, поэтому мне придется пойти с этим.

844   16  

16 ответов:

следующий код должен делать эту работу, он является кросс-платформенным и работает на Python 2.4-3.2. Я тестировал его на Windows, OS X и Linux.

from tendo import singleton
me = singleton.SingleInstance() # will sys.exit(-1) if other instance is running

доступна последняя версия кода singleton.py. пожалуйста ошибки здесь файл.

вы можете установить tend, используя один из следующих методов:

простой кросс-платформенный решение, нашли в еще вопрос by Згода:

import fcntl, sys
pid_file = 'program.pid'
fp = open(pid_file, 'w')
try:
    fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    # another instance is running
    sys.exit(0)

очень похоже на предложение С. Лотта, но с кодом.

этот код на Linux. Он использует "абстрактные" доменные сокеты UNIX, но он прост и не оставляет устаревших файлов блокировки. Я предпочитаю это решение выше, потому что оно не требует специально зарезервированного TCP-порта.

try:
    import socket
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    ## Create an abstract socket, by prefixing it with null. 
    s.bind( 'postconnect_gateway_notify_lock') 
except socket.error as e:
    error_code = e.args[0]
    error_string = e.args[1]
    print "Process already running (%d:%s ). Exiting" % ( error_code, error_string) 
    sys.exit (0) 

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

Я не знаю, достаточно ли это pythonic, но в мире Java прослушивание на определенном порту является довольно широко используемым решением, поскольку оно работает на всех основных платформах и не имеет никаких проблем с сбоями программ.

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

используйте pid-файл. У вас есть какое-то известное местоположение "/path/to/pidfile", и при запуске вы делаете что-то вроде этого (частично псевдокод, потому что я предварительно кофе и не хочу работать так сильно):

import os, os.path
pidfilePath = """/path/to/pidfile"""
if os.path.exists(pidfilePath):
   pidfile = open(pidfilePath,"r")
   pidString = pidfile.read()
   if <pidString is equal to os.getpid()>:
      # something is real weird
      Sys.exit(BADCODE)
   else:
      <use ps or pidof to see if the process with pid pidString is still running>
      if  <process with pid == 'pidString' is still running>:
          Sys.exit(ALREADAYRUNNING)
      else:
          # the previous server must have crashed
          <log server had crashed>
          <reopen pidfilePath for writing>
          pidfile.write(os.getpid())
else:
    <open pidfilePath for writing>
    pidfile.write(os.getpid())

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

никогда не писал python раньше, но это то, что я только что реализовал в mycheckpoint, чтобы предотвратить его запуск дважды или более crond:

import os
import sys
import fcntl
fh=0
def run_once():
    global fh
    fh=open(os.path.realpath(__file__),'r')
    try:
        fcntl.flock(fh,fcntl.LOCK_EX|fcntl.LOCK_NB)
    except:
        os._exit(0)

run_once()

нашел предложение Славы-Н после публикации этого в другом номере (http://stackoverflow.com/questions/2959474). этот вызывается как функция, блокирует файл исполняемых скриптов (не файл pid) и поддерживает блокировку до завершения скрипта (нормальный или ошибка).

вы уже нашли ответ на подобный вопрос в другом потоке, поэтому для полноты картины посмотрите, как добиться того же на Windows uning с именем mutex.

http://code.activestate.com/recipes/474070/

Это может работать.

  1. попытка создать PID-файл в известном месте. Если вы потерпите неудачу, кто-то заблокировал файл, вы закончили.

  2. когда вы закончите нормально, закройте и удалите файл PID, чтобы кто-то другой мог его перезаписать.

вы можете обернуть вашу программу в shell-скрипт, который удаляет файл PID даже если ваша программа аварийно завершает работу.

вы также можете использовать файл PID, чтобы убить программу, если она виснет.

использование файла блокировки является довольно распространенным подходом в unix. Если он выходит из строя, вы должны очистить вручную. Вы можете хранить PID в файле и при запуске проверить, есть ли процесс с этим PID, переопределяя файл блокировки, если нет. (Однако вам также нужна блокировка вокруг файла read-file-check-pid-rewrite-file). Вы найдете то, что вам нужно для получения и проверки пид в os-пакет. Общий способ проверить, существует ли процесс с заданным pid, состоит в том, чтобы отправить его несмертельный сигнал.

другие альтернативы могут сочетать это с семафорами flock или posix.

открытие сетевого сокета, как предложил saua, вероятно, будет самым простым и самым портативным.

для тех, кто использует wxPython для их применения, вы можете использовать функцию wx.SingleInstanceCheckerдокументы.

я лично использую подкласс wx.App который использует wx.SingleInstanceChecker и возвращает False С OnInit() если есть существующий экземпляр приложения уже выполняется вот так:

import wx

class SingleApp(wx.App):
    """
    class that extends wx.App and only permits a single running instance.
    """

    def OnInit(self):
        """
        wx.App init function that returns False if the app is already running.
        """
        self.name = "SingleApp-%s".format(wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(self.name)
        if self.instance.IsAnotherRunning():
            wx.MessageBox(
                "An instance of the application is already running", 
                "Error", 
                 wx.OK | wx.ICON_WARNING
            )
            return False
        return True

это простая замена для wx.App что запрещает несколько экземпляров. Чтобы использовать его просто заменить wx.App С SingleApp в коде вот так:

app = SingleApp(redirect=False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
frame.Show(True)
app.MainLoop()

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

решение Sorin Sbarnea работает для меня под OS X, Linux и Windows, и я благодарен за это.

, tempfile.gettempdir () ведет себя так под OS X и Windows, а другой под другим some / many / all(?) * nixes (игнорируя тот факт, что OS X также является Unix!). Разница важна для этого кода.

OS X и Windows имеют пользовательский темп каталоги, поэтому tempfile, созданный одним пользователем, не виден другому пользователю. Напротив, во многих версиях *nix (я тестировал Ubuntu 9, RHEL 5, OpenSolaris 2008 и FreeBSD 8) temp dir является /tmp для всех пользователей.

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

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

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

Я использую single_process на моем gentoo;

pip install single_process

пример:

from single_process import single_process

@single_process
def main():
    print 1

if __name__ == "__main__":
    main()   

см.:https://pypi.python.org/pypi/single_process/1.0

вот мое окончательное решение только для Windows. Поместите следующее в модуль, возможно называемый 'onlyone.py-или еще что-нибудь. Включите этот модуль непосредственно в ваш __ main __ python script file.

import win32event, win32api, winerror, time, sys, os
main_path = os.path.abspath(sys.modules['__main__'].__file__).replace("\", "/")

first = True
while True:
        mutex = win32event.CreateMutex(None, False, main_path + "_{<paste YOUR GUID HERE>}")
        if win32api.GetLastError() == 0:
            break
        win32api.CloseHandle(mutex)
        if first:
            print "Another instance of %s running, please wait for completion" % main_path
            first = False
        time.sleep(1)

объяснение

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

преимущества

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

пример linux

этот метод основан на создании временный файл автоматически удаляется после закрытия приложения. при запуске программы мы проверяем наличие файла; если файл существует (есть отложенное выполнение) , программа закрывается ; в противном случае он создает файл и продолжает выполнение программы.

from tempfile import *
import time
import os
import sys


f = NamedTemporaryFile( prefix='lock01_', delete=True) if not [f  for f in     os.listdir('/tmp') if f.find('lock01_')!=-1] else sys.exit()

YOUR CODE COMES HERE

Я продолжаю подозревать, что должно быть хорошее решение POSIXy с использованием групп процессов, без необходимости попадания в файловую систему, но я не могу его прибить. Что-то вроде:

при запуске ваш процесс отправляет 'kill -0' всем процессам в определенной группе. Если такие процессы существуют,он выходит. Затем он присоединяется к группе. Никакие другие процессы не используют эту группу.

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

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

Я думаю, что это не очень хорошее решение в конце концов, если кто-то не может улучшить его?

я столкнулся с этой точной проблемой на прошлой неделе, и хотя я нашел несколько хороших решений, я решил сделать очень простой и чистый пакет python и загрузил его в PyPI. Он отличается от tendo тем, что может блокировать любое имя строкового ресурса. Хотя вы могли бы, конечно, заблокировать __file__ для достижения того же эффекта.

установить с: pip install quicklock

использование его чрезвычайно просто:

[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> # Let's create a lock so that only one instance of a script will run
...
>>> singleton('hello world')
>>>
>>> # Let's try to do that again, this should fail
...
>>> singleton('hello world')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nate/live/gallery/env/lib/python2.7/site-packages/quicklock/quicklock.py", line 47, in singleton
    raise RuntimeError('Resource <{}> is currently locked by <Process {}: "{}">'.format(resource, other_process.pid, other_process.name()))
RuntimeError: Resource <hello world> is currently locked by <Process 24801: "python">
>>>
>>> # But if we quit this process, we release the lock automatically
...
>>> ^D
[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> singleton('hello world')
>>>
>>> # No exception was thrown, we own 'hello world'!

посмотреть: https://pypi.python.org/pypi/quicklock

Comments

    Ничего не найдено.