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

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

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

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


person Slava V    schedule 19.12.2008    source источник
comment
Возможно, ваша жизнь была бы проще, если бы вы отследили и исправили segfault. Не то чтобы это было легко сделать.   -  person David Locke    schedule 19.12.2008
comment
Его нет в моей библиотеке, он в привязках python libxml и очень застенчив - срабатывает только раз в пару дней.   -  person Slava V    schedule 20.12.2008
comment
Стандартная библиотека Python поддерживает flock (), что является правильным решением для современных программ UNIX. При открытии порта используется место в гораздо более ограниченном пространстве имен, тогда как pid-файлы более сложны, так как вам нужно проверять запущенные процессы, чтобы безопасно сделать их недействительными; у flock нет ни одной проблемы.   -  person Charles Duffy    schedule 20.12.2008
comment
В качестве альтернативы этим можно управлять вне Python с помощью утилиты командной строки flock.   -  person Acumenus    schedule 23.04.2021


Ответы (23)


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

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

person sorin    schedule 12.08.2009
comment
Работает под Linux, но только если класс определен в том же файле. То есть, если вы попытаетесь из singlething import SingleInstance, а затем использовали me = SingleInstance (), он не заблокировал бы файл. - person Lionel; 31.12.2010
comment
НЕ должны ли мы закрывать файл и разблокировать его в деструкторе в случае отсутствия окон? - person Gnu Engineer; 10.05.2011
comment
Я обновил ответ и добавил ссылку на последнюю версию. Если вы обнаружите ошибку, отправьте ее на github, и я исправлю ее как можно скорее. - person sorin; 10.05.2011
comment
Мне нравится это решение, но оно не удалось (winXP32, python-3.2). Здесь вы можете увидеть ошибку: pastebin.com/a40kj7ae Ошибка из-за неправильного пути (файл блокировки: e: \ somepath \ temp \ E: \ testLock \ test_fileLock.lock) Я изменил строку 9: использовал relpath вместо abspath. Надеюсь, ничего не сломается, если это поменять. Извините, я не знаю, как использовать github. - person Johny_M; 03.07.2011
comment
@Johny_M Спасибо, я сделал патч и выпустил новую версию на pypi.python.org/pypi/tendo < / а> - person sorin; 03.07.2011
comment
Этот синтаксис не работал у меня в Windows под Python 2.6. Что сработало для меня: 1: из импорта синглтона тендо 2: я = синглтон.SingleInstance () - person Brian; 17.08.2011
comment
Еще одна зависимость от такой тривиальной вещи? Звучит не очень привлекательно. - person WhyNotHugo; 10.08.2012
comment
Я бы предпочел, чтобы это завершилось тихо ... он выдал сообщение об ошибке Другой экземпляр уже запущен, завершается. что было весьма заметно для пользователя. - person ArtOfWarfare; 04.09.2013
comment
@ArtOfWarfare - это открытый исходный код, не стесняйтесь добавлять эту функцию, как необязательную, и я буду рад ее принять. До сих пор во всех моих вариантах использования одновременное выполнение двух экземпляров считалось чем-то исключительным, и я хотел, чтобы меня уведомляли об этом, в тех же случаях это может указывать на заблокированный экземпляр ... - person sorin; 04.09.2013
comment
Документ класса по-прежнему использует синтаксис до исправления примера кода. - person Zitrax; 07.09.2013
comment
Обрабатывает ли синглтон процессы, которые получают сигтерм (например, если процесс выполняется слишком долго), или мне нужно это обрабатывать? - person JimJty; 24.04.2014
comment
Это решение не работает. Я пробовал это на Ubuntu 14.04, запускал один и тот же скрипт из двух окон терминала одновременно. Они оба отлично бегают. - person Dimon; 09.05.2016
comment
Он работает в Ubuntu 14.04 для меня Димон, я думаю, вы, должно быть, делаете это неправильно - person JasTonAChair; 21.06.2016
comment
Не работает в Debian. (ошибка: не удалось найти подходящий дистрибутив для Requirement.parse ('install')) - person David Stein; 20.03.2018
comment
Он не работает с Python ›= 3.7 при создании исполняемого файла. - person Voldemort; 04.02.2021

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

import fcntl
import os
import sys

def instance_already_running(label="default"):
    """
    Detect if an an instance with the label is already running, globally
    at the operating system level.

    Using `os.open` ensures that the file pointer won't be closed
    by Python's garbage collector after the function's scope is exited.

    The lock will be released when the program exits, or could be
    released if the file pointer were closed.
    """

    lock_file_pointer = os.open(f"/tmp/instance_{label}.lock", os.O_WRONLY)

    try:
        fcntl.lockf(lock_file_pointer, fcntl.LOCK_EX | fcntl.LOCK_NB)
        already_running = False
    except IOError:
        already_running = True

    return already_running

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

person Slava V    schedule 21.12.2008
comment
Из любопытства: действительно ли это кроссплатформенный? На винде работает? - person Joachim Sauer; 21.12.2008
comment
В Windows нет модуля fcntl (хотя функциональность можно эмулировать). - person jfs; 21.12.2008
comment
Это не кроссплатформенный (и я не делал вид, что это так). Для Windows вам нужно использовать мьютексы для достижения аналогичного результата, но я больше не занимаюсь Windows, и у меня нет кода, которым можно поделиться. - person zgoda; 22.12.2008
comment
Проблема здесь в том, что если вы хотите записать какие-либо данные в pid_file (например, PID), вы потеряете их. См .: coding.derkeiler.com/Archive/ Python / comp.lang.python / 2008-06 / - person benno; 08.07.2009
comment
это приятно! Намного лучше, чем делать оттуда внешние вызовы ps и grep. - person Danylo Gurianov; 12.11.2013
comment
СОВЕТ: если вы хотите заключить это в функцию, 'fp' должен быть глобальным, иначе файл будет закрыт после выхода из функции. - person cmcginty; 15.05.2014
comment
Я пробовал это в течение нескольких дней, но когда приложение выходит с Control + z, файл остается заблокированным, и я больше не могу запускать приложение, пока не удалю файл pid - person Mirko; 15.05.2017
comment
@Mirko Control + Z не закрывает приложение (на любой известной мне ОС), а приостанавливает его. Приложение можно вернуть на передний план с помощью fg. Итак, похоже, что оно у вас работает правильно (т.е. приложение все еще активно, но приостановлено, поэтому блокировка остается на месте). - person Sam Bull; 01.03.2020
comment
Этот код в моей ситуации (Python 3.8.3 в Linux) требовал модификации: lock_file_pointer = os.open(lock_path, os.O_WRONLY | os.O_CREAT) - person baziorek; 16.06.2020

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

person Roberto Rosario    schedule 02.11.2009
comment
Роберто, вы уверены, что после паники ядра или жесткого сброса файл \ 0postconnect_gateway_notify_lock не будет присутствовать при загрузке? В моем случае файл сокета AF_UNIX все еще присутствует после этого, и это разрушает всю идею. Вышеупомянутое решение с установкой блокировки на конкретное имя файла в этом случае очень надежно. - person Danylo Gurianov; 12.11.2013
comment
Как отмечалось выше, это решение работает в Linux, но нет в Mac OS X. - person Bilal and Olga; 09.07.2014
comment
Это решение не работает. Пробовал на Ubuntu 14.04. Запустите один и тот же сценарий одновременно из двух окон терминала. Они оба отлично бегают. - person Dimon; 09.05.2016
comment
У меня это сработало в Ubuntu 16. И завершение процесса любым способом позволило запустить другой. Даймон, я думаю, ты сделал что-то не так в тесте. (Возможно, вы забыли перевести свой скрипт в спящий режим после выполнения приведенного выше кода, поэтому он немедленно завершил работу и освободил сокет.) - person Luke; 04.11.2017
comment
Как долго нужно спать, если на оригинальном плакате это требование не указывалось? Сон в течение одной секунды не помогал этой работе в Ubuntu 18.0.4. - person Steve Cohen; 30.05.2018
comment
Это не вопрос сна. Код работает, но только как встроенный код. Я вставлял это в функцию. Сокет исчезал, как только функция существовала. - person Steve Cohen; 30.05.2018
comment
postconnect_gateway_notify_lock - это просто произвольная строка, вы можете использовать baa_baa_black_sheep, и она по-прежнему работает нормально. Я думал, что это какая-то конкретная константа, связанная с Linux или сокетом .. - person cardamom; 21.01.2019
comment
в чем секрет того, что перед ним стоит префикс null? Когда я не помещаю этот символ, решение делает файл устаревшим. - person Ivan Talalaev; 16.09.2020
comment
Только что понял, что этот абстрактный сокет не создает фактический файл в Linux. Это уловка. - person Ivan Talalaev; 16.09.2020

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

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

person Joachim Sauer    schedule 19.12.2008
comment
+1 к этому, особенно потому, что он позволяет мне уведомлять запущенный экземпляр, поэтому он создает другое окно, всплывает и т. Д. - person WhyNotHugo; 17.06.2011
comment
Используйте, например, import socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind(('localhost', DEFINED_PORT)). OSError будет поднят, если другой процесс привязан к тому же порту. - person crishoj; 14.05.2018

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

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

person M.D. Klapwijk    schedule 31.08.2011
comment
Очень элегантный. Я изменил его, чтобы он получал путь из аргументов скрипта. Также рекомендует встроить это в какое-нибудь обычное место - пример - person Jossef Harush; 27.07.2017
comment
Я нашел эту полезную ссылку. Если вы используете Windows fctnl, заменителем Windows является win32api. Надеюсь это поможет. - person BV_Data; 13.11.2020

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

person Charlie Martin    schedule 19.12.2008
comment
Это состояние гонки. Последовательность «тест-запись» может вызвать исключение из-за того, что две программы запускаются почти одновременно, не находят файла и пытаются одновременно открыть для записи. Он должен вызвать исключение для одного, позволяя продолжить выполнение другого. - person S.Lott; 19.12.2008

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

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

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

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

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

person S.Lott    schedule 19.12.2008

Вот мое возможное решение только для Windows. Поместите следующее в модуль, возможно, с именем onlyone.py или как-нибудь еще. Включите этот модуль непосредственно в ваш __ main __ файл скрипта 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)

Объяснение

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

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

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

Лучшее решение для этого в 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 teno), который недоступен в Windows, и если вы попытаетесь заморозить свое приложение python с помощью такого пакета, как pyinstaller, который выполняет статический импорт, он выдает ошибку.

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

person Samuel Kazeem    schedule 18.02.2019
comment
У меня это не работает (я использую Python 3.6 в Windows 10) - person Vivian De Smedt; 08.09.2020

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

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

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

person Rolf Rander    schedule 19.12.2008

Любой, кто использует 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()
person Matt Coubrough    schedule 08.04.2016
comment
После кодирования потока со списком сокетов для синглтона я нашел это, которое отлично работает, и я уже установил пару программ, однако мне бы хотелось, чтобы дополнительное пробуждение, которое я мог бы дать синглтону, чтобы я мог вывести его на передний план - и-центр большой кучи перекрывающихся окон. Также: задокументированная здесь ссылка указывает на довольно бесполезную автоматически сгенерированную документацию это лучше ссылка - person RufusVS; 22.09.2016
comment
@RufusVS Вы правы - это гораздо лучшая ссылка на документацию, обновили ответ. - person Matt Coubrough; 22.09.2016

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

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

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

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

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

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

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

person Philip Semanchuk    schedule 02.12.2010
comment
Для некоторых читателей (например, меня) желаемое поведение состоит в том, что период работы может выполняться только для одной копии, независимо от того, сколько пользователей задействовано. Таким образом, каталоги tmp для каждого пользователя не работают, в то время как общий / tmp или блокировка порта демонстрируют желаемое поведение. - person Jonathan Hartley; 24.07.2013

Основываясь на ответе Роберто Росарио, я придумал следующую функцию:

SOCKET = None
def run_single_instance(uniq_name):
    try:
        import socket
        global SOCKET
        SOCKET = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        ## Create an abstract socket, by prefixing it with null.
        # this relies on a feature only in linux, when current process quits, the
        # socket will be deleted.
        SOCKET.bind('\0' + uniq_name)
        return True
    except socket.error as e:
        return False

Нам нужно определить глобальный SOCKET vaiable, поскольку он будет собираться только после завершения всего процесса. Если мы объявим в функции локальную переменную, она выйдет из области видимости после выхода из функции, и сокет будет удален.

Вся заслуга Роберто Росарио, поскольку я только поясняю и уточняю его кодекс. И этот код будет работать только в Linux, поскольку следующий цитируемый текст из https://troydhanson.github.io/network/Unix_domain_sockets.html объясняет:

В Linux есть особенность: если путь к сокету домена UNIX начинается с нулевого байта \ 0, его имя не отображается в файловой системе. Таким образом, он не будет конфликтовать с другими именами в файловой системе. Кроме того, когда сервер закрывает свой прослушивающий сокет домена UNIX в абстрактном пространстве имен, его файл удаляется; с обычными сокетами домена UNIX файл сохраняется после его закрытия сервером.

person makiko_fly    schedule 24.12.2019

Я использую 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/

person gkiwi    schedule 24.04.2014
comment
Не работает в Py3. Упаковка выглядит неправильно. - person Ekevoo; 08.09.2015
comment
В Windows я получаю: ImportError: нет модуля с именем fcntl - person Andrew W. Phillips; 24.01.2017
comment
pypi.python.org/pypi/single_process/1.0 - 404 - person Pedro Lobito; 30.12.2020

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

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

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

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

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

person Jonathan Hartley    schedule 24.07.2013

Я столкнулся с этой проблемой на прошлой неделе, и, хотя я нашел несколько хороших решений, я решил сделать очень простой и чистый пакет python и загрузил его в PyPI. Он отличается от teno тем, что может заблокировать любое строковое имя ресурса. Хотя вы, конечно, можете заблокировать __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

person Nate Ferrero    schedule 28.01.2015
comment
Я только что установил его с помощью pip install quicklock, но когда я пытаюсь использовать его с помощью синглтона из quicklock import, я получаю исключение: ImportError: невозможно импортировать имя singleton. Это на Mac. - person grayaii; 05.05.2016
comment
Оказалось, что quicklock не работает с python 3. Вот почему у меня он не работал. - person grayaii; 05.05.2016
comment
Да, извините, это вообще не было рассчитано на будущее. Я буду приветствовать вклад, чтобы он заработал! - person Nate Ferrero; 17.05.2016

Поздний ответ, но для окон вы можете использовать:

from win32event import CreateMutex
from win32api import CloseHandle, GetLastError
from winerror import ERROR_ALREADY_EXISTS
import sys

class singleinstance:
    """ Limits application to single instance """

    def __init__(self):
        self.mutexname = "testmutex_{D0E858DF-985E-4907-B7FB-8D732C3FC3B9}"
        self.mutex = CreateMutex(None, False, self.mutexname)
        self.lasterror = GetLastError()
    
    def alreadyrunning(self):
        return (self.lasterror == ERROR_ALREADY_EXISTS)
        
    def __del__(self):
        if self.mutex:
            CloseHandle(self.mutex)

использование

# do this at beginnig of your application
myapp = singleinstance()

# check is another instance of same program running
if myapp.alreadyrunning():
    print ("Another instance of this program is already running")
    sys.exit(1)
person Pedro Lobito    schedule 30.12.2020

Вот пример кроссплатформенности, который я тестировал на Windows Server 2016 и Ubuntu 20.04 с использованием Python. 3.7.9:

import os

class SingleInstanceChecker:
    def __init__(self, id):
        if isWin():
            ensure_win32api()
            self.mutexname = id
            self.lock = win32event.CreateMutex(None, False, self.mutexname)
            self.running = (win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS)

        else:
            ensure_fcntl()
            self.lock = open(f"/tmp/isnstance_{id}.lock", 'wb')
            try:
                fcntl.lockf(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
                self.running = False
            except IOError:
                self.running = True


    def already_running(self):
        return self.running
        
    def __del__(self):
        if self.lock:
            try:
                if isWin():
                    win32api.CloseHandle(self.lock)
                else:
                    os.close(self.lock)
            except Exception as ex:
                pass

# ---------------------------------------
# Utility Functions
# Dynamically load win32api on demand
# Install with: pip install pywin32
win32api=winerror=win32event=None
def ensure_win32api():
    global win32api,winerror,win32event
    if win32api is None:
        import win32api
        import winerror
        import win32event


# Dynamically load fcntl on demand
# Install with: pip install fcntl
fcntl=None
def ensure_fcntl():
    global fcntl
    if fcntl is None:
        import fcntl


def isWin():
    return (os.name == 'nt')
# ---------------------------------------

Вот он в употреблении:

import time, sys

def main(argv):
    _timeout = 10
    print("main() called. sleeping for %s seconds" % _timeout)
    time.sleep(_timeout)
    print("DONE")


if __name__ == '__main__':
    SCR_NAME = "my_script"
    sic = SingleInstanceChecker(SCR_NAME)
    if sic.already_running():
        print("An instance of {} is already running.".format(SCR_NAME))
        sys.exit(1)
    else:
        main(sys.argv[1:])
person Timothy C. Quinn    schedule 02.02.2021

пример 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
person kerwal    schedule 14.10.2015
comment
Добро пожаловать в Stack Overflow! Хотя этот ответ может быть правильным, пожалуйста, добавьте некоторые пояснения. Передача базовой логики более важна, чем просто предоставление кода, потому что это помогает OP и другим читателям исправить эту и аналогичные проблемы самостоятельно. - person CodeMouse92; 14.10.2015
comment
Это потокобезопасно? Похоже, проверка и создание временного файла не атомарны ... - person coppit; 17.07.2017

В системе 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, если ограничение должно применяться ко всем пользователям. Отказ от ответственности: а) предполагается, что (базовое) имя скрипта уникально, б) могут быть условия гонки.

person user71769    schedule 17.01.2019

Вот хороший пример для django с contextmanager и memcached: https://docs.celeryproject.org/en/latest/tutorials/task-cookbook.html.

Может использоваться для защиты одновременной работы на разных хостах. Может использоваться для управления несколькими задачами. Также можно изменить для простых скриптов Python.

Моя модификация приведенного выше кода находится здесь:

import time
from contextlib import contextmanager
from django.core.cache import cache


@contextmanager
def memcache_lock(lock_key, lock_value, lock_expire):
    timeout_at = time.monotonic() + lock_expire - 3

    # cache.add fails if the key already exists
    status = cache.add(lock_key, lock_value, lock_expire)
    try:
        yield status
    finally:
        # memcache delete is very slow, but we have to use it to take
        # advantage of using add() for atomic locking
        if time.monotonic() < timeout_at and status:
            # don't release the lock if we exceeded the timeout
            # to lessen the chance of releasing an expired lock owned by someone else
            # also don't release the lock if we didn't acquire it
            cache.delete(lock_key)


LOCK_EXPIRE = 60 * 10  # Lock expires in 10 minutes


def main():
    lock_name, lock_value = "lock_1", "locked"
    with memcache_lock(lock_name, lock_value, LOCK_EXPIRE) as acquired:
        if acquired:
            # single instance code here:
            pass


if __name__ == "__main__":
    main()
person Rufat    schedule 28.02.2021

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

Может использоваться для управления несколькими задачами.

import os
from contextlib import contextmanager
from time import sleep


class ExceptionTaskInProgress(Exception):
    pass


# Context manager for suppressing exceptions
class SuppressException:
    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        return True


# Context manager for task
class TaskSingleInstance:
    def __init__(self, task_name, lock_path):
        self.task_name = task_name
        self.lock_path = lock_path
        self.lock_filename = os.path.join(self.lock_path, self.task_name + ".lock")

        if os.path.exists(self.lock_filename):
            raise ExceptionTaskInProgress("Resource already in use")

    def __enter__(self):
        self.fl = open(self.lock_filename, "w")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.fl.close()
        os.unlink(self.lock_filename)


# Here the task is silently interrupted
# if it is already running on another instance.
def main1():
    task_name = "task1"
    tmp_filename_path = "."
    with SuppressException():
        with TaskSingleInstance(task_name, tmp_filename_path):
            print("The task `{}` has started.".format(task_name))
            # The single task instance code is here.
            sleep(5)
            print("The task `{}` has completed.".format(task_name))


# Here the task is interrupted with a message
# if it is already running in another instance.
def main2():
    task_name = "task1"
    tmp_filename_path = "."
    try:
        with TaskSingleInstance(task_name, tmp_filename_path):
            print("The task `{}` has started.".format(task_name))
            # The single task instance code is here.
            sleep(5)
            print("Task `{}` completed.".format(task_name))
    except ExceptionTaskInProgress as ex:
        print("The task `{}` is already running.".format(task_name))


if __name__ == "__main__":
    main1()
    main2()
person Rufat    schedule 28.02.2021

person    schedule
comment
Добро пожаловать в Stack Overflow! Хотя этот блок кода может ответить на вопрос, было бы лучше, если бы вы могли дать небольшое объяснение того, почему он это делает. Пожалуйста, отредактируйте свой ответ, включив такое описание. - person Artjom B.; 22.10.2018