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

StackOverflow https://stackoverflow.com/questions/380870

Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

Никогда раньше не писал 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) и сохраняет блокировку до тех пор, пока сценарий не завершится (обычно или при ошибке).

Используйте 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 вместо старого.Тогда продолжайте.

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

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

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

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

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

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

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

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

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

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

Для всех, кто использует 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()

Вот мое возможное решение только для Windows.Поместите следующее в модуль, возможно, с названием «onlyone.py» или как-то так.Включите этот модуль непосредственно в ваш __ основной __ файл сценария Python.

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)

Объяснение

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

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

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

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

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

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

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

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

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

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

я использую 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 является использование мьютексов, предложенных @zgoda.

import win32event
import win32api
from winerror import ERROR_ALREADY_EXISTS

mutex = win32event.CreateMutex(None, False, 'name')
last_error = win32api.GetLastError()

if last_error == ERROR_ALREADY_EXISTS:
   print("App instance already running")

В некоторых ответах используется fctnl (также включен в пакет @sorin tendo), который недоступен в Windows, и если вы попытаетесь заморозить свое приложение Python с помощью такого пакета, как pyinstaller который выполняет статический импорт, выдает ошибку.

Кроме того, используя метод файла блокировки, создается read-only проблема с файлами базы данных (было такое с sqlite3).

Я продолжаю подозревать, что должно быть хорошее решение 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

пример 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

В системе Linux можно также спроситьpgrep -a Для количества экземпляров сценарий найден в списке процессов (опция -a раскрывает полную строку командной строки).Например.

import os
import sys
import subprocess

procOut = subprocess.check_output( "/bin/pgrep -u $UID -a python", shell=True, 
                                   executable="/bin/bash", universal_newlines=True)

if procOut.count( os.path.basename(__file__)) > 1 :        
    sys.exit( ("found another instance of >{}<, quitting."
              ).format( os.path.basename(__file__)))

Удалять -u $UID если ограничение должно распространяться на все пользователи.Отказ от ответственности:а) предполагается, что (базовое) имя сценария уникально, б) могут возникнуть условия гонки.

import sys,os

# start program
try:  # (1)
    os.unlink('lock')  # (2)
    fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (3)  
except: 
    try: fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (4) 
    except:  
        print "Another Program running !.."  # (5)
        sys.exit()  

# your program  ...
# ...

# exit program
try: os.close(fd)  # (6)
except: pass
try: os.unlink('lock')  
except: pass
sys.exit()  
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top