문제

프로그램의 인스턴스를 하나만 실행하는 Python 방식이 있습니까?

내가 생각해낸 유일한 합리적인 해결책은 일부 포트에서 서버로 실행하려고 시도한 다음 동일한 포트에 바인딩하려는 두 번째 프로그램이 실패하는 것입니다.하지만 별로 좋은 아이디어는 아닙니다. 이보다 더 가벼운 것이 있을까요?

(프로그램이 때때로 실패할 것으로 예상된다는 점을 고려하십시오.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

최신 코드 버전을 사용할 수 있습니다 Singleton.py. 제발 여기에 버그를 파일.

다음 방법 중 하나를 사용하여 경향을 설치할 수 있습니다.

다른 팁

단순한, 크로스 플랫폼 발견 된 솔루션 다른 질문 ~에 의해 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)

S.Lott의 제안과 비슷하지만 코드와 비슷합니다.

이 코드는 Linux에 따라 다릅니다. 'Abstract'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을 작성하지 않았지만 Crond에 의해 두 번 이상 시작되는 것을 방지하기 위해 MyCheckpoint에서 방금 구현 한 것입니다.

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)에 이것을 게시 한 후 Slava-N의 제안을 발견했습니다. 이것을 함수라고 불리며, 실행 스크립트 파일 (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())

다시 말해, pidfile이 존재하는지 확인하고 있습니다. 그렇지 않은 경우 PID를 해당 파일에 작성하십시오. pidfile이 존재하는 경우 PID가 러닝 프로세스의 PID인지 확인하십시오. 그렇다면 다른 라이브 프로세스가 실행되므로 종료하십시오. 그렇지 않은 경우 이전 프로세스가 충돌하여 로그인 한 다음 이전 프로세스 대신 파일에 자신의 PID를 작성하십시오. 그런 다음 계속하십시오.

이미 다른 스레드에서 비슷한 질문에 대한 답변을 찾았으므로 완전성을 위해 Windows Uning에서 Mutx라는 동일한 방법을 달성하는 방법을 확인하십시오.

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

이것은 효과가있을 수 있습니다.

  1. 시도 된 위치로 PID 파일을 만듭니다. 실패하면 누군가가 파일을 잠겨 있으면 완료되었습니다.

  2. 정상적으로 완료되면 PID 파일을 닫고 제거하여 다른 사람이이를 덮어 쓸 수 있습니다.

프로그램이 중단 되더라도 PID 파일을 제거하는 쉘 스크립트로 프로그램을 랩핑 할 수 있습니다.

PID 파일을 사용하여 프로그램이 중단되면 프로그램을 죽일 수도 있습니다.

잠금 파일을 사용하는 것은 유닉스에서 매우 일반적인 접근법입니다. 충돌하면 수동으로 정리해야합니다. PID를 파일에 저장하고 시작 시이 PID와 프로세스가 있는지 확인하여 잠금 파일을 재정의하지 않으면 확인하십시오. (그러나 읽기 파일 체크 피드-리파이트 파일 주위에 잠금 장치가 필요합니다). PID를 얻고 확인하는 데 필요한 것을 찾을 수 있습니다. OS-패키지. 주어진 PID가있는 프로세스가 있는지 확인하는 일반적인 방법은 치명적이지 않은 신호를 보내는 것입니다.

다른 대안은 이것을 무리 또는 posix semaphores와 결합 할 수 있습니다.

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()

다음은 최종 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)

설명

코드는 스크립트의 전체 경로에서 파생 된 이름의 뮤텍스를 만들려고합니다. 우리는 실제 파일 시스템과의 혼란을 피하기 위해 포워드 슬래시를 사용합니다.

장점

  • 구성 또는 '마법'식별자가 필요하지 않으므로 필요한만큼 다양한 스크립트에서 사용하십시오.
  • 부실한 파일이 남지 않으면 Mutx는 당신과 함께 죽습니다.
  • 기다릴 때 유용한 메시지를 인쇄합니다

저는 새로운 사용자이고 Stack Overflow에서 아직 투표를 허용하지 않기 때문에 답변으로 게시합니다.

Sorin Sbarnea의 솔루션은 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의 솔루션도 다중 사용자 시스템에서 오작동한다는 점은 주목할 가치가 있습니다.

나는 사용한다 single_process 내 젠투에;

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에서 제안한대로 Mutxes를 사용하는 것입니다.

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 솔루션이 있어야한다고 생각하지만, 그것을 못 박았을 수는 없습니다. 같은 것 :

스타트 업시 프로세스는 특정 그룹의 모든 프로세스에 '킬 -0'을 보냅니다. 그러한 프로세스가 존재하면 종료됩니다. 그런 다음 그룹에 합류합니다. 다른 프로세스는 해당 그룹을 사용하지 않습니다.

그러나 이것은 레이스 조건이 있습니다. 여러 프로세스가 모두 정확히 동시에이 작업을 수행 할 수 있으며 모두 그룹에 가입하고 동시에 실행됩니다. 방수를 위해 어떤 종류의 뮤트를 추가 할 때까지 더 이상 프로세스 그룹이 필요하지 않습니다.

당신의 프로세스가 1 분 또는 시간마다 한 번 또는 시간마다 Cron에 의해서만 시작되면 허용 될 수 있지만, 원하지 않는 날에 정확하게 잘못 될 것이라는 것이 약간 긴장하게 만듭니다.

누군가가 개선 할 수 없다면 이것이 아주 좋은 해결책이 아니라고 생각합니까?

나는 지난 주 에이 정확한 문제를 겪었고, 좋은 솔루션을 찾았지만 매우 간단하고 깨끗한 파이썬 패키지를 만들고 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

리눅스 예제

이 방법은 응용 프로그램을 닫은 후 자동으로 삭제 된 임시 파일 생성을 기반으로합니다. 프로그램을 시작하여 파일의 존재를 확인합니다. 파일이 존재하는 경우 (보류중인 실행이 있음) 프로그램이 닫힙니다. 그렇지 않으면 파일을 생성하고 프로그램 실행을 계속합니다.

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 제한이 신청 해야하는 경우 모두 사용자. 면책 조항 : a) 스크립트의 (기본) 이름이 독특하다고 가정합니다. b) 인종 조건이있을 수 있습니다.

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