我需要确保用户一次只能运行一个程序实例 这意味着,我必须以编程方式检查相同的程序是否已经运行,并在这种情况下退出。
我想到的第一件事就是在程序启动时在某处创建一个文件。然后,程序的每个其他实例将检查该文件并在找到它时退出 问题是,程序必须始终正常退出并能够删除它创建的文件,以便工作。 如果停电,锁定文件仍然存在,程序无法再次启动。
为了解决这个问题,我决定将第一个程序的进程ID存储到锁定文件中,当另一个实例启动时,它会检查文件中的PID是否附加到某个正在运行的进程中。
如果文件不存在,为空,或者PID与任何现有进程不对应,程序将继续运行并将自己的PID写入文件。
这似乎工作得很好 - 即使在意外关机后,(现在过时的)进程ID与其他程序相关联的可能性似乎也很低。
但它仍然感觉不对( 有可能被一些不相关的进程锁定)并且使用进程ID似乎超出了标准C ++并且可能不是非常便携任
那么,还有另一种(更干净,更安全)的方法吗?理想情况下,它可以使用ISO 98 C ++标准,适用于Windows和* nix 如果它不能独立于平台,那么Linux / Unix对我来说是一个优先考虑的问题。
答案 0 :(得分:33)
您可以使用多种方法来完成仅允许应用程序的一个实例:
方法1:全局同步对象或内存
通常通过创建命名的全局互斥或事件来完成。如果已经创建,那么您知道该程序已在运行。
例如在Windows中你可以这样做:
#define APPLICATION_INSTANCE_MUTEX_NAME "{BA49C45E-B29A-4359-A07C-51B65B5571AD}"
//Make sure at most one instance of the tool is running
HANDLE hMutexOneInstance(::CreateMutex( NULL, TRUE, APPLICATION_INSTANCE_MUTEX_NAME));
bool bAlreadyRunning((::GetLastError() == ERROR_ALREADY_EXISTS));
if (hMutexOneInstance == NULL || bAlreadyRunning)
{
if(hMutexOneInstance)
{
::ReleaseMutex(hMutexOneInstance);
::CloseHandle(hMutexOneInstance);
}
throw std::exception("The application is already running");
}
方法2:锁定文件,第二个程序无法打开文件,所以它打开
您还可以通过在应用程序打开时将其锁定来专门打开文件。如果文件已经独占打开,并且您的应用程序无法接收文件句柄,则表示该程序已在运行。在Windows上,您不会在使用FILE_SHARE_WRITE
API打开的文件上指定共享标记CreateFile
。在linux上你可以使用flock
。
方法3:搜索流程名称:
您可以枚举活动进程并搜索具有进程名称的进程。
答案 1 :(得分:5)
将进程pid写入文件的方法是在许多不同的已建立应用程序中使用的常见方法。事实上,如果您现在查看/var/run
目录,我打赌您已经找到了几个*.pid
文件。
正如你所说,它不是100%强大,因为有可能混淆pids。我听说过使用flock()
锁定特定于应用程序的文件的程序,当进程退出时,该文件将由操作系统自动解锁,但这种方法更具特定于平台且不太透明。
答案 2 :(得分:5)
小心,a single-instance program is its own denial of service。
该链接描述了单实例检测的安全问题,并建议使用安全方法,因此至少低权限进程无法利用该问题。
答案 3 :(得分:4)
禁止多个程序实例运行是非常unix的。
如果程序是网络守护程序,它不需要主动禁止多个实例 - 只有第一个实例才能监听套接字,因此后续实例会自动弹出。如果它是,例如,RDBMS,它不需要主动禁止多个实例 - 只有第一个实例才能打开并锁定文件。等
答案 4 :(得分:3)
我实际上使用了您描述的过程,除了在您突然耗尽磁盘空间且无法再创建文件时发生的边缘情况之外,它工作正常。
执行此操作的“正确”方法可能是使用共享内存:http://www.cs.cf.ac.uk/Dave/C/node27.html
答案 5 :(得分:1)
如果你想要一些标准的东西,那么使用一个文件作为“锁定”几乎是要走的路。它确实有你提到的缺点(如果你的应用没有清理,重启可能是一个问题)。
这种方法被很多应用程序使用,但我唯一能想到的就是VMware。是的,有些时候你必须进入并删除'* .lck',当事情被楔入。
使用Brian Bondy提到的全局互斥或其他系统对象是一种更好的方法,但这些是特定于平台的(除非您使用其他库来抽象平台细节)。
答案 6 :(得分:0)
我没有一个好的解决方案,但有两个想法:
您可以添加ping功能来查询其他进程,并确保它不是一个不相关的进程。 Firefox在Linux上做了类似的事情,并且在已经运行的情况下不启动新实例。
如果您使用信号处理程序,则可以确保除kill -9
答案 7 :(得分:0)
我扫描进程列表,查找具有匹配命令行参数的应用程序可执行文件的名称,如果匹配则退出。
我的应用可以运行多次,但我不希望它同时运行相同的配置文件。
显然,这是Windows专用的,但是任何* NIX系统上的相同概念都非常简单,即使没有特定的库,只需打开shell命令'ps -ef'或变体并查找你的应用程序。
'*************************************************************************
' Sub: CheckForProcess()
' Author: Ron Savage
' Date: 10/31/2007
'
' This routine checks for a running process of this app with the same
' command line parameters.
'*************************************************************************
Private Function CheckForProcess(ByVal processText As String) As Boolean
Dim isRunning As Boolean = False
Dim search As New ManagementObjectSearcher("SELECT * FROM Win32_process")
Dim info As ManagementObject
Dim procName As String = ""
Dim procId As String = ""
Dim procCommandLine As String = ""
For Each info In search.Get()
If (IsNothing(info.Properties("Name").Value)) Then procName = "NULL" Else procName = Split(info.Properties("Name").Value.ToString, ".")(0)
If (IsNothing(info.Properties("ProcessId").Value)) Then procId = "NULL" Else procId = info.Properties("ProcessId").Value.ToString
If (IsNothing(info.Properties("CommandLine").Value)) Then procCommandLine = "NULL" Else procCommandLine = info.Properties("CommandLine").Value.ToString
If (Not procId.Equals(Me.processId) And procName.Equals(processName) And procCommandLine.Contains(processText)) Then
isRunning = True
End If
Next
Return (isRunning)
End Function
答案 8 :(得分:0)
我找到了一种使用boost/interprocess/sync/named_mutex
执行此操作的跨平台方法。请参阅此答案https://stackoverflow.com/a/42882353/4806882