Убедитесь, что запущен только один экземпляр программы - PullRequest
104 голосов
/ 19 декабря 2008

Есть ли у Pythonic способ запустить только один экземпляр программы?

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

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

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

Ответы [ 19 ]

88 голосов
/ 12 августа 2009

Следующий код должен выполнять эту работу, он кроссплатформенный и работает на 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 . Пожалуйста файл ошибок здесь .

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

34 голосов
/ 21 декабря 2008

Простое, кроссплатформенное решение, найдено в другой вопрос от zgoda :

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)

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

26 голосов
/ 02 ноября 2009

Этот код специфичен для 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( '\0postconnect_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 можно изменить, чтобы разрешить нескольким программам, которым требуется один экземпляр.

20 голосов
/ 19 декабря 2008

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

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

11 голосов
/ 31 августа 2011

Никогда прежде не писал 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()

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

10 голосов
/ 19 декабря 2008

Используйте файл 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 в этот файл. Если pid-файл существует, проверьте, является ли pid pid запущенного процесса; если так, то у вас запущен еще один живой процесс, так что просто выключите его. Если нет, то предыдущий процесс завершился сбоем, поэтому запишите его, а затем запишите свой собственный pid в файл вместо старого. Затем продолжайте.

6 голосов
/ 22 декабря 2008

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

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

5 голосов
/ 19 декабря 2008

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

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

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

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

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

3 голосов
/ 08 апреля 2016

Для тех, кто использует 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()
3 голосов
/ 19 декабря 2008

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

Другие альтернативы могут сочетать это с семафорами стаи или посикс.

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

...