2008-10-21 5 views
30

WxPython의 GUI 응용 프로그램에서 작업하고 있는데, 응용 프로그램의 복사본이 컴퓨터에서 한 번만 실행되도록하는 방법을 확신 할 수 없습니다. 응용 프로그램의 특성상 두 번 이상 실행하는 것은 의미가 없으므로 신속하게 실패합니다. Win32에서 간단히 명명 된 뮤텍스를 만들고 시작할 때 확인해 볼 수 있습니다. 불행히도, 나는 이것을 할 수있는 리눅스의 어떤 기능을 모른다.리눅스에서 응용 프로그램의 단일 인스턴스를 확인하십시오.

예기치 않게 응용 프로그램이 비정상적으로 종료되면 자동으로 해제 될 내용을 찾고 있습니다. 나는 사용자가 추락했기 때문에 수동으로 잠금 파일을 삭제해야하는 부담으로 사용자를 부담시키고 싶지 않습니다.

답변

23

세마포어 사용과 같은 몇 가지 공통 기술이 있습니다. 가장 자주 사용되는 것으로 보이는 것은 실행중인 프로세스의 PID를 포함하는 시작시 "pid lock 파일"을 만드는 것입니다. 프로그램이 시작될 때 파일이 이미 존재한다면, 파일을 열고 안에있는 pid를 가져 와서,/proc/pid가 인 cmdline 값을 확인하려면 해당 pid를 가진 프로세스가 실행 중인지 확인하십시오 당신의 프로그램의 인스턴스입니다, 만약 종료된다면, 그렇지 않으면 당신의 pid로 파일을 덮어 씁니다. pid 파일의 일반적인 이름은 application_name.pid입니다.

+1

관습에 따라, 이것은/var/run/아래에 있어야합니다. –

+0

호기심에서 독점적 인 액세스를 위해 파일을 여는 것만으로 뮤텍스가 작동하지 않습니까? – Menkboy

+0

Menkboy, 충돌이 발생했을 때 파일이 제대로 닫히면 완벽하게 작동하고 문제가 더욱 단순해질 것이라고 생각합니다. 고맙습니다. –

0

잠금 파일을 만들고 그 안에 PID를 넣으면 프로세스 ID를 확인하고 충돌이 발생했는지 여부를 알 수 있습니다.

나는 이것을 개인적으로하지 않았으므로 적절한 양의 소금과 함께 섭취하십시오. : p

+2

는 PID 파일이 일반적인 사용하지만, 문제가없는 것은 아니다. 하나 들어, 경쟁 조건이있을 수 있습니다. 둘째, 앱이 종료되면 정리되지 않을 수 있습니다. –

0

'pidof'유틸리티를 사용할 수 있습니까? 앱이 실행 중이면 pidof는 앱의 프로세스 ID를 stdout에 씁니다. 그렇지 않으면, 개행 문자 (LF)를 인쇄하고 오류 코드를 리턴합니다.

예 (간략화를 위해 떠들썩한 파티에서)

가장 일반적인 방법은 오직 PID 포함 .pid은/var/실행/호출 [어플리케이션]로 파일을 삭제하는 단연
linux# pidof myapp 
8947 
linux# pidof nonexistent_app 

linux# 
+0

이것은 잠금 파일 접근 방식보다 코드가 적게 필요합니다. 단점은 정확히 원자가 아니지만 단일 인스턴스 감지의 경우 필요하지 않을 수 있습니다. –

+0

앱 내부에서 "pidof app"을 실행중인 경우이 기능이 작동하지 않습니다. – MattSmith

+3

동일한 이름으로 실행중인 다른 프로그램이 있거나 다른 사용자가 프로그램을 실행중인 경우에는 작동하지 않습니다. – CesarB

0

실행중인 프로세스 또는 상위 프로세스. 다른 방법으로 활성 프로세스에 메시지를 보낼 수 있도록 동일한 디렉토리에 명명 된 파이프를 만들 수 있습니다. 새 파일을 엽니 다.

+0

PID 파일을 사용하는 것이 일반적이지만 문제가되지는 않습니다. 하나 들어, 경쟁 조건이있을 수 있습니다. 둘째, 앱이 종료되면 정리되지 않을 수 있습니다. –

1

유닉스에서 SYSV 세마포어와 인터페이스하는 파이썬 모듈을 찾으십시오. 세마포어에는 SEM_UNDO 플래그가있어 프로세스가 충돌하면 프로세스가 보유한 리소스가 해제됩니다. 버나드 제안

그렇지 않으면, 당신은

import os 
os.getpid() 

를 사용하여 응용 프로그램 이름을/ .pid을 실행은/var /에 기록 할 수 있습니다. 프로세스가 시작될 때/var/run/application_name.pid에있는 pid가 ps 테이블에 나열되어 있는지 확인하고 그렇지 않으면 종료합니다. 그렇지 않으면/var/run/application_name .pid에 자체 pid를 씁니다. . 다음 var_run_pid에서 당신은에서 읽은 PID는/var에/실행/응용 프로그램 이름 .pid

cmd = "ps -p %s -o comm=" % var_run_pid 
app_name = os.popen(cmd).read().strip() 
if len(app_name) > 0: 
    Already running 
+0

SYSV 세마포어를 제안하는 경우 +1,보다 효율적인 것이 아닌'ps'를 호출 할 것을 제안하는 경우 -1 (말하자면'kill -0' - 여분의 fork/exec를 피하는 경우 명령보다는 신호 호출을 통해) 경합 상태와 PID 충돌을 피하기 위해 조언 잠금 (즉, 무리)없이 pid 파일을 제안합니다. –

58

똑바로 살아라는 자문 잠금 flock(LOCK_EX)을 사용하고; Python에서는 fcntl module에 있습니다.

pidfiles와 달리 이러한 잠금은 프로세스가 어떤 이유로 종료 될 때 항상 자동으로 해제되며 파일 삭제와 관련된 경쟁 조건이 없습니다 (파일에 이 잠금을 해제하기 위해 삭제되어야 함). PID를 상속하는 다른 프로세스가 생길 가능성이 없기 때문에 오래된 잠금을 확인하는 것처럼 보입니다.

깔끔하게 종료 감지가 필요하면 잠금 장치를 잡은 후 파일에 마커 (예 : 사용자의 PID)를 기록한 다음 정상 종료 전에 파일을 0 바이트 상태로자를 수 있습니다 (자물쇠가 열리고 있음); 따라서 잠금이 유지되지 않고 파일이 비어 있지 않으면 부정한 종료가 표시됩니다. fcntl 모듈을 사용

+1

나는 이것이 아마도 대부분의 경우에 가장 좋은 방법 일 것이라고 동의한다. –

+1

이것은 올바른 잠금 방법이지만, 정상 종료시 정리 된 PID 파일을 작성하고/var/lock/subsys (존재하는 경우)에 항목을 작성하는 것도 좋습니다. 이렇게하면 프로그램이 충돌 등으로 인해 다시 시작되는지 여부를 알 수 있습니다. 그래서, 둘 다하는 것이 도움이됩니다. –

+0

@tinkertim - 두 개 이상 가지고 있지 않고 pidfile을 flock()하는 것이 좋습니다. –

24

전체 잠금 솔루션 :

import fcntl 
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(1) 
+2

잠금 파일이 모든 사용자에게 동일하다고 가정하면 잠금이 유용하기 때문에 쓰기 권한 문제가 발생할 수 있습니다. 나는 대답에서이 문제를 기술하고 언급했다. –

+0

이유는 모르겠지만 파이썬 3.4.1에서는 작동하지 않습니다. 두 개의 인스턴스가 오류없이 실행됩니다. – boreq

+0

@boreq, 테스트하는 방법 (사용중인 파일 시스템, 예상되는 동작, 실제 동작 등)에 대해 좀 더 명확하게 표현할 수 있습니까? –

8

wxWidgets에이 목적을 위해 wxSingleInstanceChecker 클래스를 제공합니다 wxPython doc, 또는 wxWidgets doc을. wxWidgets에의 문서는 C++의 예제 코드를 가지고 있지만, 파이썬 해당하는이 같은 (테스트되지 않은)해야한다 : 당신이 통과 할 수 있도록 할 때 나는 이러한 종류의 응용 프로그램을 실행하기위한 기본 프레임 워크를했습니다

name = "MyApp-%s" % wx.GetUserId() 
    checker = wx.SingleInstanceChecker(name) 
    if checker.IsAnotherRunning(): 
     return False 
0

시도한 후속 인스턴스의 명령 행 인수를 첫 번째 인수로 전달합니다. 인스턴스는 이미 수신 대기중인 인스턴스를 찾지 못하면 미리 정의 된 포트에서 수신을 시작합니다. 인스턴스가 이미 존재하면 명령 행 인수를 소켓을 통해 송신하고 종료합니다.

code w/ explanation

6

이 사용자 zgoda하여 answer을 기반으로 구축. 주로 잠금 파일에 대한 쓰기 액세스와 관련된 까다로운 문제를 해결합니다. 특히 잠금 파일이 처음으로 root에 의해 생성 된 경우 다른 사용자 foo은 사용자 foo에 대한 쓰기 권한이 없기 때문에이 파일을 더 이상 성공적으로 다시 작성할 수 없습니다. 분명한 해결책은 모든 사람에게 쓰기 권한을 가진 파일을 만드는 것입니다. 이 솔루션은 또한 사용자가 다른 사용자 정의 권한을 사용하여 파일을 만들어야하는 번거 로움으로 인해 다른 answer을 기반으로합니다. 이 우려는 실제 프로그램에서 root을 비롯한 모든 사용자가 프로그램을 실행할 수있는 경우에 중요합니다.

import fcntl, os, stat, tempfile 

app_name = 'myapp' # <-- Customize this value 

# Establish lock file settings 
lf_name = '.{}.lock'.format(app_name) 
lf_path = os.path.join(tempfile.gettempdir(), lf_name) 
lf_flags = os.O_WRONLY | os.O_CREAT 
lf_mode = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH # This is 0o222, i.e. 146 

# Create lock file 
# Regarding umask, see https://stackoverflow.com/a/15015748/832230 
umask_original = os.umask(0) 
try: 
    lf_fd = os.open(lf_path, lf_flags, lf_mode) 
finally: 
    os.umask(umask_original) 

# Try locking the file 
try: 
    fcntl.lockf(lf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) 
except IOError: 
    msg = ('Error: {} may already be running. Only one instance of it ' 
      'can run at a time.' 
      ).format('appname') 
    exit(msg) 

위 코드의 제한 사항은 잠금 파일이 예기치 않은 권한으로 이미 존재하면 해당 권한이 수정되지 않는다는 것입니다.

/var/run/<appname>/을 잠금 파일의 디렉토리로 사용하고 싶지만이 디렉토리를 만들려면 root 권한이 필요합니다. 사용할 디렉토리를 직접 결정할 수 있습니다.

잠금 파일에 대한 파일 핸들을 열 필요가 없음에 유의하십시오.

4

다음은 TCP 포트 기반의 솔루션입니다 :

# Use a listening socket as a mutex against multiple invocations 
import socket 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.bind(('127.0.0.1', 5080)) 
s.listen(1) 
관련 문제