프로그램의 인스턴스를 하나만 실행하는 Python 방식이 있습니까?
내가 생각해 낸 유일한 합리적인 해결책은 일부 포트에서 서버로 실행하려고 시도한 다음 동일한 포트에 바인딩하려는 두 번째 프로그램이 실패합니다. 하지만 정말 좋은 생각이 아닙니다. 아마도 이것보다 더 가벼운 것이 있을까요?
(프로그램이 때때로 실패 할 것으로 예상된다는 점을 고려하십시오. 즉, segfault- "잠금 파일"과 같은 것은 작동하지 않습니다)
프로그램의 인스턴스를 하나만 실행하는 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 입니다. 제발 여기에 파일 버그 .
다음 방법 중 하나를 사용하여 tend를 설치할 수 있습니다.
easy_install tendo
pip install tendo
간단한, 크로스 플랫폼 솔루션에서 발견 된 또 다른 질문 으로 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
S.Lott의 제안과 매우 비슷하지만 코드가 있습니다.
fcntl
Windows 에는 모듈 이 없습니다 (기능을 에뮬레이션 할 수 있음).
fg
. 따라서 제대로 작동하는 것처럼 들립니다 (예 : 앱이 여전히 활성 상태이지만 일시 중지되어 잠금 상태가 유지됨).
lock_file_pointer = os.open(lock_path, os.O_WRONLY | os.O_CREAT)
이 코드는 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 포트 또는 명명 된 파이프를 사용하는지 또는 '그래도).
import socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind(('localhost', DEFINED_PORT))
. OSError
다른 프로세스가 동일한 포트에 바인딩되면 An 이 발생합니다.
전에는 파이썬을 작성하지 않았지만 이것은 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에서 동일한 결과를 얻는 방법을 참조하십시오.
잠금 파일을 사용하는 것은 유닉스에서 매우 일반적인 접근 방식입니다. 충돌이 발생하면 수동으로 정리해야합니다. 파일에 PID를 저장하고 시작할 때이 PID가있는 프로세스가 있는지 확인하고 그렇지 않은 경우 잠금 파일을 재정의 할 수 있습니다. (그러나 read-file-check-pid-rewrite-file에 대한 잠금도 필요합니다.) OS 에서 pid를 얻고 확인하는 데 필요한 것을 찾을 수 있습니다. 패키지 . 주어진 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()
다음은 최종 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)
코드는 스크립트의 전체 경로에서 파생 된 이름으로 뮤텍스를 생성하려고합니다. 실제 파일 시스템과의 잠재적 인 혼동을 피하기 위해 슬래시를 사용합니다.
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
은 Windows에서 사용할 수없는 (@sorin tendo 패키지에도 포함되어 있음) 사용하며 pyinstaller
정적 가져 오기를 수행 하는 것과 같은 패키지를 사용하여 Python 앱을 고정하려고 하면 오류가 발생합니다.
또한 잠금 파일 방법을 사용하면 read-only
데이터베이스 파일에 문제가 발생합니다 sqlite3
.
나는 새로운 사용자이고 Stack Overflow가 아직 투표를 허용하지 않기 때문에 이것을 답변으로 게시하고 있습니다.
Sorin Sbarnea의 솔루션은 OS X, Linux 및 Windows에서 작동하며 그것에 대해 감사합니다.
그러나 tempfile.gettempdir ()은 OS X 및 Windows에서 한 가지 방식으로 작동하고 다른 some / many / all (?) * nixes에서 다른 방식으로 작동합니다 (OS X도 Unix라는 사실을 무시합니다!). 차이점은이 코드에서 중요합니다.
OS X 및 Windows에는 사용자 별 임시 디렉토리가 있으므로 한 사용자가 만든 임시 파일은 다른 사용자에게 표시되지 않습니다. 대조적으로 * nix의 많은 버전 (우분투 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()
나는 파일 시스템에 부딪히지 않고 프로세스 그룹을 사용하는 좋은 POSIXy 솔루션이 있어야한다고 계속해서 의심하고 있지만, 그럴 수는 없다. 다음과 같은 것 :
시작시 프로세스는 특정 그룹의 모든 프로세스에 'kill -0'을 보냅니다. 그러한 프로세스가 있으면 종료됩니다. 그런 다음 그룹에 합류합니다. 다른 프로세스는 해당 그룹을 사용하지 않습니다.
그러나 여기에는 경쟁 조건이 있습니다. 여러 프로세스가 모두 정확하게 동시에이 작업을 수행 할 수 있으며 결국 모두 그룹에 합류하여 동시에 실행됩니다. 방수 처리를 위해 일종의 뮤텍스를 추가 할 때까지 더 이상 프로세스 그룹이 필요하지 않습니다.
프로세스가 매분 또는 매시간 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'!
Roberto Rosario의 답변을 바탕으로 다음과 같은 기능을 제안합니다.
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 을 정의해야합니다 . 함수에서 지역 변수를 선언하면 함수가 종료 된 후 범위를 벗어나 소켓이 삭제됩니다.
나는 그의 코드를 명확하고 정교하게 설명하기 때문에 모든 신용은 Roberto Rosario에게 가야합니다. 그리고이 코드는 https://troydhanson.github.io/network/Unix_domain_sockets.html 에서 인용 된 다음 텍스트가 설명 하는 것처럼 Linux에서만 작동 합니다.
Linux에는 특별한 기능이 있습니다. UNIX 도메인 소켓의 경로 이름이 널 바이트 \ 0으로 시작하면 해당 이름이 파일 시스템에 매핑되지 않습니다. 따라서 파일 시스템의 다른 이름과 충돌하지 않습니다. 또한 서버가 추상 네임 스페이스에서 UNIX 도메인 수신 소켓을 닫으면 해당 파일이 삭제됩니다. 일반 UNIX 도메인 소켓을 사용하면 서버가 파일을 닫은 후에도 파일이 유지됩니다.
리눅스 예
이 방법은 응용 프로그램을 닫은 후 자동으로 삭제되는 임시 파일 생성을 기반으로합니다. 프로그램 시작 우리는 파일의 존재를 확인합니다. 파일이 존재하면 (보류중인 실행이 있음) 프로그램이 닫힙니다. 그렇지 않으면 파일을 만들고 프로그램 실행을 계속합니다.
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()