我正在使用 WxPython 开发一个 GUI 应用程序,但我不确定如何确保在任何给定时间在机器上只运行我的应用程序的一个副本。由于应用程序的性质,多次运行没有任何意义,并且会很快失败。在 Win32 下,我可以简单地创建一个命名互斥体并在启动时检查它。不幸的是,我不知道 Linux 中有任何工具可以做到这一点。
如果应用程序意外崩溃,我正在寻找将自动释放的东西。我不想让我的用户因为崩溃而不得不手动删除锁定文件。
我正在使用 WxPython 开发一个 GUI 应用程序,但我不确定如何确保在任何给定时间在机器上只运行我的应用程序的一个副本。由于应用程序的性质,多次运行没有任何意义,并且会很快失败。在 Win32 下,我可以简单地创建一个命名互斥体并在启动时检查它。不幸的是,我不知道 Linux 中有任何工具可以做到这一点。
如果应用程序意外崩溃,我正在寻找将自动释放的东西。我不想让我的用户因为崩溃而不得不手动删除锁定文件。
正确的做法是使用建议锁定flock(LOCK_EX)
;在 Python 中,这可以在fcntl
模块中找到。
与 pidfiles 不同,这些锁总是在您的进程因任何原因终止时自动释放,不存在与文件删除相关的竞争条件(因为不需要删除文件来释放锁),并且没有机会不同进程继承了 PID,因此似乎验证了一个陈旧的锁。
如果你想要不干净的关机检测,你可以在获取锁之后在文件中写入一个标记(例如你的PID,对于传统主义者),然后在干净关机之前将文件截断为0字节状态(同时持有锁); 因此,如果未持有锁且文件非空,则指示不正常关闭。
fcntl
使用模块的完整锁定解决方案:
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)
有几种常见的技术,包括使用信号量。我看到最常用的一个是在启动时创建一个“pid 锁定文件”,其中包含正在运行的进程的 pid。如果程序启动时文件已经存在,打开它并抓取里面的 pid,检查是否有具有该 pid 的进程正在运行,如果是则检查 /proc/ pid中的 cmdline 值是否为你的程序的实例,如果它然后退出,否则用你的 pid 覆盖文件。pid 文件的通常名称是application_name.pid
。
wxWidgets 为此提供了一个 wxSingleInstanceChecker 类:wxPython doc或wxWidgets doc。wxWidgets 文档有 C++ 中的示例代码,但 python 等价物应该是这样的(未经测试):
name = "MyApp-%s" % wx.GetUserId()
checker = wx.SingleInstanceChecker(name)
if checker.IsAnotherRunning():
return False
这建立在用户zgoda的答案之上。它主要解决了与锁定文件的写访问有关的棘手问题。特别是,如果锁定文件是由 最初创建的,则由于用户 没有写入权限,另一个用户将无法再成功地尝试重写此文件。显而易见的解决方案似乎是为每个人创建具有写入权限的文件。该解决方案还建立在我的不同答案之上,必须创建具有此类自定义权限的文件。这个问题在现实世界中很重要,您的程序可能由任何用户运行,包括.root
foo
foo
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
权限。您可以自行决定使用哪个目录。
请注意,无需打开锁定文件的文件句柄。
这是基于 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)
semaphore.h
我相信,在-- sem_open()
、等中定义的函数集sem_trywait()
是 POSIX 等价的。
在 unix 上寻找一个与 SYSV 信号量接口的 python 模块。信号量有一个 SEM_UNDO 标志,如果进程崩溃,它将导致进程持有的资源被释放。
否则,正如伯纳德建议的那样,您可以使用
import os
os.getpid()
并将其写入 /var/run/ application_name .pid。当进程启动时,它应该检查 /var/run/ application_name .pid 中的 pid 是否在 ps 表中列出,如果是则退出,否则将自己的 pid 写入 /var/run/ application_name .pid。下面的 var_run_pid 是您从 /var/run/ application_name .pid读取的 pid
cmd = "ps -p %s -o comm=" % var_run_pid
app_name = os.popen(cmd).read().strip()
if len(app_name) > 0:
Already running
如果您创建一个锁定文件并将 pid 放入其中,您可以对照它检查您的进程 id 并判断您是否崩溃了,不是吗?
我没有亲自这样做过,所以服用适量的盐。:p
您可以使用“pidof”实用程序吗?如果您的应用程序正在运行,pidof 会将您的应用程序的进程 ID 写入标准输出。如果不是,它将打印一个换行符 (LF) 并返回一个错误代码。
示例(来自 bash,为简单起见):
linux# pidof myapp
8947
linux# pidof nonexistent_app
linux#
到目前为止,最常用的方法是将一个名为 [application].pid 的文件放入 /var/run/ 中,该文件仅包含正在运行的进程或父进程的 PID。作为替代方案,您可以在同一目录中创建一个命名管道,以便能够向活动进程发送消息,例如打开一个新文件。
当您希望能够将后续尝试实例的命令行参数传递给第一个实例时,我已经为运行这些类型的应用程序创建了一个基本框架。如果一个实例没有找到一个已经在监听的实例,它就会开始监听一个预定义的端口。如果实例已经存在,它会通过套接字发送其命令行参数并退出。