2008-10-05 57 views
21

Necesito asegurarme de que el usuario pueda ejecutar solo una instancia de mi programa a la vez.
Lo que significa que tengo que verificar programáticamente si el mismo programa ya se está ejecutando y lo dejo en ese caso.¿Cómo bloquear la ejecución de dos instancias del mismo programa?

Lo primero que me vino a la mente fue crear un archivo en algún lugar, cuando se inicia el programa. Luego, cada otra instancia del programa verificará este archivo y saldrá si lo encontró.
El problema es que el programa siempre debe salir correctamente y poder eliminar el archivo que creó, para que esto funcione. En caso de, por ejemplo, un corte de energía, el archivo de bloqueo permanece en su lugar y no se puede volver a iniciar el programa.

Para resolver esto, decidí almacenar el ID del proceso del primer programa en el archivo de bloqueo y cuando se inicia otra instancia, comprueba si el PID del archivo está conectado a algún proceso en ejecución.
Si el archivo no existe, está vacío o el PID no corresponde a ningún proceso existente, el programa continúa ejecutándose y escribe su propio PID en el archivo.

Parece que funciona bastante bien: incluso después de un apagado inesperado, la posibilidad de que el ID del proceso (ahora obsoleto) se asocie con algún otro programa, parece ser bastante bajo.

Pero todavía no se siente bien (hay es una posibilidad de quedar bloqueado por algún proceso no relacionado) y trabajar con identificadores de proceso parece ir más allá del estándar C++ y probablemente tampoco sea muy portátil.

Entonces, ¿hay otra forma (más limpia y segura) de hacer esto? Idealmente, debería funcionar con el estándar ISO 98 C++ y con Windows y * nix por igual.
Si no se puede hacer de plataforma independiente, Linux/Unix es una prioridad para mí.

Respuesta

30

Hay varios métodos que puede utilizar para llevar a cabo sólo permite una instancia de su aplicación:

Método 1: Global objeto de sincronización o memoria

Generalmente se realiza creando un mutex o evento global con nombre. Si ya está creado, sabrá que el programa ya se está ejecutando.

Por ejemplo, en ventanas que podría hacer:

#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"); 
    } 

Método 2: bloqueo de un archivo, segundo programa no puede abrir el archivo, por lo que es abierta

También puede abrir exclusivamente una archivo bloqueándolo en la aplicación abierta. Si el archivo ya está abierto exclusivamente y su aplicación no puede recibir un identificador de archivo, significa que el programa ya se está ejecutando. En Windows, simplemente no debe especificar los indicadores de uso compartido FILE_SHARE_WRITE en el archivo que está abriendo con CreateFile API. En Linux usarías rebaño.

Método 3: Búsqueda de nombre de proceso:

Se podría enumerar los procesos activos y la búsqueda de uno con su nombre de proceso.

+1

si dos personas copiar el código que podría terminar con el mismo APPLICATION_INSTANCE_MUTEX_NAME: -> –

+0

ya que deben utilizar su propia cadena única :) Me hizo pensar en cambiarlo a la mía aunque :) es –

+0

Hay una variante de Linux C++ del Método 1? (específicamente C++ 11) ¡Sería increíble ver un ejemplo también! –

3

De hecho, uso exactamente el proceso que describes, y funciona bien, excepto en el caso extremo que ocurre cuando de repente se queda sin espacio en el disco y ya no puede crear archivos.

La forma "correcta" de hacer esto es probablemente usar la memoria compartida: http://www.cs.cf.ac.uk/Dave/C/node27.html

5

Su método para escribir el proceso pid en un archivo es común y se usa en muchas aplicaciones establecidas diferentes. De hecho, si busca en su directorio /var/run, apuesto a que ya encontrará varios archivos *.pid.

Como dices, no es 100% robusto porque existe la posibilidad de que los pids se confundan. He oído hablar de programas que usan flock() para bloquear un archivo específico de la aplicación que el sistema operativo desbloqueará automáticamente cuando finalice el proceso, pero este método es más específico de la plataforma y menos transparente.

+0

Creo que una razón por la que muchas aplicaciones de UNIX aún usan archivos de bloqueo se debe a que el bloqueo de archivos en NFS puede o no valer la pena: http://en.wikipedia.org/wiki/File_locking#Problems – bk1e

+0

El problema con el archivo pid es eso (dado que pid se otorga secuencialmente en * nix) si el archivo no se elimina correctamente, es muy fácil que otro proceso adquiera el mismo pid la próxima vez que se inicie la computadora (lo que significa que su programa se negará a ejecutarse). Por lo tanto, se ve obligado a verificar si el pid está asociado al nombre correcto del proceso, pero el usuario puede cambiar fácilmente los nombres de los procesos, lo que significa que un usuario informado puede invalidar fácilmente el método. (si no me equivoco) – Malabarba

+0

@BruceConnor: Es cierto, no es 100% robusto. Puede que le interese saber que [OpenBSD] (http://openbsd.org/) asigna nuevos pids utilizando un generador de números aleatorios. –

0

no tengo una buena solución, pero dos pensamientos:

  1. Se podría añadir una capacidad de ping para consultar el otro proceso y asegurarse de que no es un proceso no relacionado. Firefox hace algo similar en Linux y no inicia una nueva instancia cuando ya se está ejecutando.

  2. Si utiliza un controlador de señal, puede asegurarse el archivo pid se elimina en todos menos un kill -9

0

que escanear la lista de procesos buscando el nombre de mis aplicaciones ejecutables que se combina con los parámetros de línea de comandos luego salga si hay una coincidencia.

Mi aplicación puede ejecutarse más de una vez, pero no quiero que ejecute el mismo archivo de configuración al mismo tiempo.

Obviamente, esto es específico de Windows, pero el mismo concepto es bastante fácil en cualquier sistema * NIX incluso sin bibliotecas específicas simplemente abriendo el comando de shell 'ps -ef' o una variación y buscando su aplicación.

'************************************************************************* 
    '  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 
+0

Esto adolece de la condición de carrera de tener otro proceso iniciado entre consultar al sistema y regresar a la persona que llama diciendo que no se encontró. Usar el enfoque mutex es una forma de evitarlo. –

1

Si quieres algo que es estándar pantano, a continuación, utilizando un archivo como un 'bloqueo' es más o menos el camino a seguir. Tiene el inconveniente que usted mencionó (si su aplicación no se limpia, reiniciar puede ser un problema).

Este método es utilizado por bastantes aplicaciones, pero el único que puedo recordar es el de VMware. Y sí, hay momentos en los que tienes que entrar y eliminar el '* .lck' cuando las cosas se bloquean.

Usar un mutex global u otro objeto del sistema mencionado en Brian Bondy es una mejor manera de hacerlo, pero estos son específicos de la plataforma (a menos que use alguna otra biblioteca para abstraer los detalles de la plataforma).

4

Es muy poco común prohibir que se ejecuten varias instancias de un programa.

Si el programa es, por ejemplo, un daemon de red, no es necesario que prohíba activamente varias instancias; solo la primera instancia escucha el socket, por lo que las instancias posteriores se activan automáticamente. Si se trata, por ejemplo, de un RDBMS, no es necesario que prohíba activamente varias instancias; solo la primera instancia puede abrir y bloquear los archivos. etc.

+0

Es un programa que debe ejecutarse junto con otro daemon que se comporte de esta manera (debido a la escucha del socket). Por lo tanto, considero que es una buena idea hacer lo mismo, pero no tengo ningún socket o base de datos para usar como bloqueo. –

+0

@ Shadow: ¿Puedes preguntarle al daemon? – wnoise

+0

¿No puede el daemon hacer cumplir esto por sí solo, permitiendo que un solo programa se comunique con él (o haga lo que sea necesario)? – DrPizza

Cuestiones relacionadas