2008-10-05 14 views
21

Należy upewnić się, że użytkownik może uruchomić tylko jedną instancję mojego programu naraz.
Co oznacza, że ​​muszę sprawdzić programowo, czy ten sam program już działa, i zakończyć w takim przypadku.Jak zablokować uruchamianie dwóch wystąpień tego samego programu?

Pierwszą rzeczą, która przyszła mi do głowy, było stworzenie pliku gdzieś, kiedy program się uruchamia. Następnie każda inna instancja programu sprawdzi, czy plik jest taki i zakończy działanie, jeśli go znalazł.
Problem polega na tym, że program musi zawsze wychodzić z wdziękiem i móc usunąć utworzony przez siebie plik, aby to działało. W przypadku, powiedzmy, przerwy w zasilaniu, plik blokady pozostaje w miejscu i program nie może zostać uruchomiony ponownie.

Aby rozwiązać ten problem, postanowiłem zapisać identyfikator procesu pierwszego programu w pliku blokady, a po uruchomieniu innej instancji sprawdza, czy PID z pliku jest dołączony do niektórych uruchomionych procesów.
Jeśli plik nie istnieje, jest pusty lub PID nie odpowiada żadnemu istniejącemu procesowi, program kontynuuje działanie i zapisuje swój własny PID do pliku.

Wydaje się to działać całkiem dobrze - nawet po nieoczekiwanym zamknięciu szansa, że ​​(teraz przestarzały) identyfikator procesu będzie powiązany z jakimś innym programem, wydaje się być dość niski.

Ale nadal nie czuje się dobrze (istnieje jest szansa na zablokowanie przez jakiś niepowiązany proces) i praca z identyfikatorami procesu wydaje się wykraczać poza standardowe C++ i prawdopodobnie nie jest zbyt przenośna.

Czy istnieje inny (bardziej czysty i bezpieczny) sposób na zrobienie tego? Najlepiej byłoby pracować ze standardem ISO 98 C++ oraz Windows i * nix.
Jeśli nie można tego zrobić niezależnie od platformy, Linux/Unix jest dla mnie priorytetem.

Odpowiedz

30

Istnieje kilka metod, za pomocą których można osiągnąć wyłącznie dzięki czemu jedno wystąpienie aplikacji:

Metoda 1: Global obiekt lub pamięć synchronizacji

Zwykle robi się to przez tworzenie nazwanego globalnego muteksu lub zdarzenia. Jeśli jest już utworzony, oznacza to, że program jest już uruchomiony.

Na przykład w oknach można zrobić:

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

Metoda 2: Blokowanie pliku, drugi program nie może otworzyć pliku, więc jest otwarty

Można też wyłącznie otworzyć plik przez zamknięcie go na otwartej aplikacji. Jeśli plik jest już otwarty, a aplikacja nie może odebrać uchwytu pliku, oznacza to, że program już działa. W oknach po prostu nie określisz flag udostępniania FILE_SHARE_WRITE na pliku otwierasz za pomocą API CreateFile. Na Linuksie używałbyś stada.

Metoda 3: Szukaj Nazwa procesu:

Można wyliczyć aktywne procesy i szukać jednej z nazwą procesu.

+1

Jeśli dwie osoby skopiować kod mogliby skończyć z tym samym APPLICATION_INSTANCE_MUTEX_NAME: -> –

+0

ya powinni wykorzystywać swój własny, unikalny ciąg :) I nie myślę o zmieniając go z kopalni chociaż jest :) –

+0

istnieje wersja C++ dla Linux C++? (konkretnie C++ 11) Byłoby wspaniale zobaczyć również przykład! –

3

Używam dokładnie opisywanego procesu i działa on dobrze z wyjątkiem przypadku krawędzi, który ma miejsce, gdy nagle zabraknie miejsca na dysku i nie można już tworzyć plików.

„poprawny” sposób to zrobić, to prawdopodobnie korzystać z pamięci współdzielonej: http://www.cs.cf.ac.uk/Dave/C/node27.html

5

Twoja metoda zapisu pid procesu do pliku jest popularna i używana w wielu różnych aplikacjach. W rzeczywistości, jeśli teraz zajrzysz do swojego katalogu /var/run, założę się, że znajdziesz już kilka plików *.pid.

Jak sama nazwa wskazuje, nie jest w 100% niezawodna, ponieważ istnieje ryzyko, że pacjenci będą zdezorientowani. Słyszałem o programach używających flock() do blokowania pliku specyficznego dla aplikacji, który będzie automatycznie odblokowywany przez system operacyjny po zakończeniu procesu, ale ta metoda jest bardziej specyficzna dla platformy i mniej przejrzysta.

+0

Myślę, że jednym z powodów, dla których wiele aplikacji systemu UNIX nadal używa plików blokujących, jest to, że blokowanie plików w systemie plików NFS może być lub może nie być nic warte: http://en.wikipedia.org/wiki/File_locking#Problems – bk1e

+0

Problem z plikiem pid jest (ponieważ pid są przyznawane sekwencyjnie w * nix), jeśli plik nie został poprawnie usunięty, bardzo łatwo inny proces może uzyskać ten sam identyfikator przy następnym uruchomieniu komputera (co oznacza, że ​​twój program odmówi uruchomienia). Więc jesteś zmuszony sprawdzić, czy pid jest powiązany z właściwą nazwą procesu, ale nazwy procesów mogą być łatwo zmieniane przez użytkownika, co oznacza, że ​​kompetentny użytkownik może łatwo unieważnić tę metodę. (jeśli się nie mylę) – Malabarba

+0

@BruceConnor: To prawda, to nie jest w 100% wytrzymałe. Być może zainteresuje cię to, że [OpenBSD] (http://openbsd.org/) przydziela nowe pidy za pomocą generatora liczb losowych. –

0

Nie mam dobre rozwiązanie, ale dwie myśli:

  1. można dodać funkcję ping kwerendy inny proces i upewnij się, że nie jest to niezwiązane proces. Firefox robi coś podobnego w Linuksie i nie uruchamia nowej instancji, gdy już jest uruchomiona.

  2. przypadku korzystania z obsługi sygnału, można zapewnić plik PID jest usuwana na wszystko ale kill -9

0

zeskanować listę procesów szuka nazwy moich aplikacji wykonywalnych z pasującymi parametrami wiersza poleceń następnie wyjdź, jeśli jest odpowiednik.

Moja aplikacja może działać więcej niż jeden raz, ale nie chcę, aby uruchomiła ten sam plik konfiguracyjny w tym samym czasie.

Oczywiście jest to specyficzne dla systemu Windows, ale ta sama koncepcja jest całkiem łatwa w każdym systemie * NIX, nawet bez konkretnych bibliotek, po prostu otwierając polecenie powłoki "ps -ef" lub odmianę i szukając aplikacji.

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

To cierpi na stan wyścigu związany z uruchomieniem innego procesu między wysyłaniem zapytania do systemu i zwracaniem się do osoby dzwoniącej z informacją, że nie została znaleziona. Korzystanie z podejścia mutex jest jednym ze sposobów obejścia tego. –

1

Jeśli chcesz czegoś, co jest standardem bagiennym, użycie pliku jako "blokady" jest prawie do zrobienia. Ma tę wadę, o której wspomniałeś (jeśli twoja aplikacja nie posprząta, ponowne uruchomienie może być problematyczne).

Ta metoda jest używana przez wiele aplikacji, ale jedyną, którą mogę sobie przypomnieć z góry mojej głowy, jest VMware. I tak, są chwile, kiedy musisz wejść i usunąć "* .lck", gdy coś się zaklinuje.

Używanie globalnego muteksu lub innego obiektu systemowego, o którym wspomina Brian Bondy, jest lepszym rozwiązaniem, ale są one zależne od platformy (chyba że używasz innej biblioteki do szczegółowego opisu szczegółów platformy).

4

Jest bardzo nieuniksowa, aby uniemożliwić uruchamianie wielu wystąpień programu.

Jeśli program jest, powiedzmy, demonem sieciowym, nie musi aktywnie blokować wielu instancji - tylko pierwsza instancja będzie nasłuchiwać gniazda, więc kolejne wystąpienia będą automatycznie bombardować. Jeśli jest to, powiedzmy, RDBMS, nie musi aktywnie blokować wielu instancji - tylko pierwsze wystąpienie otwiera i blokuje pliki. itp.

+0

To program, który powinien działać razem z innym demonem, który zachowuje się w ten sposób (ze względu na nasłuchiwanie w gnieździe). Dlatego uważam, że dobrym pomysłem jest zrobić to samo - ale nie mam żadnego gniazda ani bazy danych do użycia jako zamka. –

+0

@Shadow: Czy możesz poprosić demona? – wnoise

+0

Czy demon nie może wymusić tego wszystkiego przez samo, pozwalając tylko jednemu programowi komunikować się z nim (lub robić to, co jest potrzebne)? – DrPizza

Powiązane problemy