Assicurati che sia in esecuzione una sola istanza di un programma


120

Esiste un modo pitonico per avere solo un'istanza di un programma in esecuzione?

L'unica soluzione ragionevole che ho trovato è provare a eseguirlo come server su una porta, quindi il secondo programma che cerca di collegarsi alla stessa porta - fallisce. Ma non è proprio una grande idea, forse c'è qualcosa di più leggero di questo?

(Prendi in considerazione che il programma dovrebbe fallire a volte, cioè segfault - quindi cose come "file di blocco" non funzioneranno)


1
Forse la tua vita sarebbe più facile se rintracciassi e aggiustassi il segfault. Non che sia una cosa facile da fare.
David Locke

Non è nella mia libreria, è nei collegamenti libxml di Python ed è estremamente timido: si attiva solo una volta al paio di giorni.
Slava V

5
La libreria standard di Python supporta flock (), che è la cosa giusta per i moderni programmi UNIX. L'apertura di una porta utilizza un punto in uno spazio dei nomi molto più limitato, mentre i file pid sono più complessi in quanto è necessario controllare i processi in esecuzione per invalidarli in modo sicuro; il gregge non ha nessun problema.
Charles Duffy,

s / UNIX / linux / ecco fatto, FTFY.
kaleissin

Risposte:


100

Il codice seguente dovrebbe fare il lavoro, è multipiattaforma e funziona su Python 2.4-3.2. L'ho testato su Windows, OS X e Linux.

from tendo import singleton
me = singleton.SingleInstance() # will sys.exit(-1) if other instance is running

L'ultima versione del codice è disponibile singleton.py . Si prega di segnalare i bug qui .

Puoi installare tend utilizzando uno dei seguenti metodi:


2
Ho aggiornato la risposta e incluso un collegamento all'ultima versione. Se trovi un bug, invialo a github e lo risolverò il prima possibile.
sorin

2
@Johny_M Grazie, ho realizzato una patch e rilasciato una versione più recente su pypi.python.org/pypi/tendo
sorin

2
Questa sintassi non ha funzionato per me su Windows con Python 2.6. Quello che ha funzionato per me era: 1: from tendo import singleton 2: me = singleton.SingleInstance ()
Brian

25
Un'altra dipendenza per qualcosa di così banale? Non sembra molto attraente.
WhyNotHugo

2
Il singleton gestisce i processi che ottengono un sigterm (ad esempio se un processo è in esecuzione troppo a lungo) o devo gestirlo?
JimJty

43

Soluzione semplice e multipiattaforma , trovata in un'altra domanda di zgoda :

import fcntl
import os
import sys

def instance_already_running(label="default"):
    """
    Detect if an an instance with the label is already running, globally
    at the operating system level.

    Using `os.open` ensures that the file pointer won't be closed
    by Python's garbage collector after the function's scope is exited.

    The lock will be released when the program exits, or could be
    released if the file pointer were closed.
    """

    lock_file_pointer = os.open(f"/tmp/instance_{label}.lock", os.O_WRONLY)

    try:
        fcntl.lockf(lock_file_pointer, fcntl.LOCK_EX | fcntl.LOCK_NB)
        already_running = False
    except IOError:
        already_running = True

    return already_running

Un po 'come il suggerimento di S.Lott, ma con il codice.


Per curiosità: è davvero multipiattaforma? Funziona su Windows?
Joachim Sauer,

1
Non esiste alcun fcntlmodulo su Windows (sebbene la funzionalità possa essere emulata).
jfs

10
SUGGERIMENTO: se vuoi racchiudere questo in una funzione 'fp' deve essere globale o il file verrà chiuso dopo che la funzione è uscita.
cmcginty

1
@ Mirko Control + Z non esce da un'applicazione (su qualsiasi sistema operativo di cui sono a conoscenza), la sospende. L'applicazione può essere riportata in primo piano con fg. Quindi, sembra che funzioni correttamente per te (cioè l'app è ancora attiva, ma sospesa, quindi il blocco rimane in posizione).
Sam Bull

1
Questo codice nella mia situazione (Python 3.8.3 su Linux) necessitava di modifiche:lock_file_pointer = os.open(lock_path, os.O_WRONLY | os.O_CREAT)
baziorek

30

Questo codice è specifico per Linux. Utilizza socket di dominio UNIX "astratti", ma è semplice e non lascia file di blocco obsoleti. Lo preferisco alla soluzione sopra perché non richiede una porta TCP appositamente riservata.

try:
    import socket
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    ## Create an abstract socket, by prefixing it with null. 
    s.bind( '\0postconnect_gateway_notify_lock') 
except socket.error as e:
    error_code = e.args[0]
    error_string = e.args[1]
    print "Process already running (%d:%s ). Exiting" % ( error_code, error_string) 
    sys.exit (0) 

La stringa univoca postconnect_gateway_notify_lockpuò essere modificata per consentire l'applicazione di più programmi che richiedono l'applicazione di una singola istanza.


1
Roberto, sei sicuro che dopo il panico del kernel o l'hard reset, il file \ 0postconnect_gateway_notify_lock non sarà presente all'avvio? Nel mio caso il file socket AF_UNIX è ancora presente dopo questo e questo distrugge l'intera idea. La soluzione sopra con l'acquisizione del blocco su un nome file specifico è molto affidabile in questo caso.
Danylo Gurianov

2
Come notato sopra, questa soluzione funziona su Linux ma non su Mac OS X.
Bilal e Olga

2
Questa soluzione non funziona. L'ho provato su Ubuntu 14.04. Esegui lo stesso script da 2 finestre di terminale contemporaneamente. Entrambi funzionano bene.
Dimon

1
Questo ha funzionato per me in Ubuntu 16. E interrompere il processo con qualsiasi mezzo ha permesso a un altro di iniziare. Dimon Penso che tu abbia fatto qualcosa di sbagliato nel tuo test. (Forse ti sei dimenticato di far dormire lo script dopo che il codice sopra è stato eseguito, quindi è uscito immediatamente e ha rilasciato il socket.)
Luca

1
Non è questione di sonno. Il codice funziona ma solo come codice inline. Lo stavo inserendo in una funzione. Il socket stava scomparendo non appena la funzione esisteva.
Steve Cohen

25

Non so se sia abbastanza pitonico, ma nel mondo Java l'ascolto su una porta definita è una soluzione abbastanza ampiamente utilizzata, poiché funziona su tutte le principali piattaforme e non ha problemi con i programmi in crash.

Un altro vantaggio dell'ascolto di una porta è che puoi inviare un comando all'istanza in esecuzione. Ad esempio, quando gli utenti avviano il programma una seconda volta, è possibile inviare all'istanza in esecuzione un comando per dirgli di aprire un'altra finestra (questo è ciò che fa Firefox, ad esempio. Non so se usano porte TCP o named pipe o qualcosa del genere, "però).


+1 a questo, specialmente perché mi consente di notificare l'istanza in esecuzione, quindi crea un'altra finestra, si apre, ecc.
WhyNotHugo

1
Usa ad es import socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind(('localhost', DEFINED_PORT)). Una OSErrorsarà generato se un altro processo è vincolato alla stessa porta.
crishoj

13

Non ho mai scritto python prima, ma questo è quello che ho appena implementato in mycheckpoint, per evitare che venga avviato due o più volte da crond:

import os
import sys
import fcntl
fh=0
def run_once():
    global fh
    fh=open(os.path.realpath(__file__),'r')
    try:
        fcntl.flock(fh,fcntl.LOCK_EX|fcntl.LOCK_NB)
    except:
        os._exit(0)

run_once()

Ho trovato il suggerimento di Slava-N dopo averlo pubblicato in un altro numero (http://stackoverflow.com/questions/2959474). Questo è chiamato come funzione, blocca il file degli script in esecuzione (non un file pid) e mantiene il blocco fino al termine dello script (normale o errore).


1
Molto elegante. L'ho cambiato in modo che ottenga il percorso dagli argomenti dello script. Raccomanda inoltre di incorporarlo in un luogo comune - Esempio
Jossef Harush

10

Usa un file pid. Hai una posizione nota, "/ path / to / pidfile" e all'avvio fai qualcosa di simile (parzialmente pseudocodice perché sono pre-caffè e non voglio lavorare così tanto):

import os, os.path
pidfilePath = """/path/to/pidfile"""
if os.path.exists(pidfilePath):
   pidfile = open(pidfilePath,"r")
   pidString = pidfile.read()
   if <pidString is equal to os.getpid()>:
      # something is real weird
      Sys.exit(BADCODE)
   else:
      <use ps or pidof to see if the process with pid pidString is still running>
      if  <process with pid == 'pidString' is still running>:
          Sys.exit(ALREADAYRUNNING)
      else:
          # the previous server must have crashed
          <log server had crashed>
          <reopen pidfilePath for writing>
          pidfile.write(os.getpid())
else:
    <open pidfilePath for writing>
    pidfile.write(os.getpid())

Quindi, in altre parole, stai controllando se esiste un pidfile; in caso contrario, scrivi il tuo pid su quel file. Se il pidfile esiste, controlla per vedere se il pid è il pid di un processo in esecuzione; in tal caso, hai un altro processo live in esecuzione, quindi spegnilo. In caso contrario, il processo precedente si è arrestato in modo anomalo, quindi registralo e quindi scrivi il tuo pid nel file al posto di quello vecchio. Quindi continua.


4
Questo ha una condizione di gara. La sequenza test-then-write può sollevare un'eccezione in quanto due programmi si avviano quasi contemporaneamente, non trovano alcun file e tentano di aprirsi per la scrittura contemporaneamente. Si dovrebbe sollevare un'eccezione su uno, permettendo all'altro per procedere.
S.Lott


5

Questo potrebbe funzionare.

  1. Tenta di creare un file PID in una posizione nota. Se fallisci, qualcuno ha bloccato il file, il gioco è fatto.

  2. Quando finisci normalmente, chiudi e rimuovi il file PID, in modo che qualcun altro possa sovrascriverlo.

Puoi racchiudere il tuo programma in uno script di shell che rimuove il file PID anche se il programma va in crash.

Puoi anche usare il file PID per terminare il programma se si blocca.


3

L'uso di un file di blocco è un approccio abbastanza comune su Unix. Se si blocca, devi ripulirlo manualmente. È possibile memorizzare il PID nel file e all'avvio controllare se c'è un processo con questo PID, ignorando il file di blocco in caso contrario. (Tuttavia, è necessario anche un blocco attorno al file read-file-check-pid-rewrite-file). Troverai ciò di cui hai bisogno per ottenere e controllare il pid nel sistema operativo pacchetto . Il modo comune per verificare se esiste un processo con un dato pid, è inviargli un segnale non fatale.

Altre alternative potrebbero essere combinare questo con semafori flock o posix.

L'apertura di una presa di rete, come proposto da saua, sarebbe probabilmente la più semplice e la più portabile.


3

Per chiunque utilizzi wxPython per la propria applicazione, è possibile utilizzare la funzione wx.SingleInstanceChecker documentata qui .

Personalmente uso una sottoclasse di wx.Appcui fa uso wx.SingleInstanceCheckere Falseda cui ritorna OnInit()se esiste un'istanza esistente dell'app già in esecuzione in questo modo:

import wx

class SingleApp(wx.App):
    """
    class that extends wx.App and only permits a single running instance.
    """

    def OnInit(self):
        """
        wx.App init function that returns False if the app is already running.
        """
        self.name = "SingleApp-%s".format(wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(self.name)
        if self.instance.IsAnotherRunning():
            wx.MessageBox(
                "An instance of the application is already running", 
                "Error", 
                 wx.OK | wx.ICON_WARNING
            )
            return False
        return True

Questo è un semplice sostituto immediato di wx.Appciò che proibisce più istanze. Per usarlo è sufficiente sostituire wx.Appcon SingleAppnel codice in questo modo:

app = SingleApp(redirect=False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
frame.Show(True)
app.MainLoop()

Dopo aver codificato un thread di elenco dei socket per un singleton ho trovato questo, che funziona alla grande e l'ho già installato in un paio di programmi, tuttavia, vorrei il "wakeup" aggiuntivo che posso dare al singleton in modo da poterlo portare al fronte e centro di una grande pila di finestre sovrapposte. Inoltre: il collegamento "documentato qui" punta a una documentazione generata automaticamente piuttosto inutile, questo è un collegamento migliore
RufusVS

@RufusVS Hai ragione - questo è un collegamento alla documentazione molto migliore, ho aggiornato la risposta.
Matt Coubrough

3

Ecco la mia eventuale soluzione solo per Windows. Metti quanto segue in un modulo, forse chiamato "onlyone.py" o qualsiasi altra cosa. Includi quel modulo direttamente nel tuo file script __ main __ python.

import win32event, win32api, winerror, time, sys, os
main_path = os.path.abspath(sys.modules['__main__'].__file__).replace("\\", "/")

first = True
while True:
        mutex = win32event.CreateMutex(None, False, main_path + "_{<paste YOUR GUID HERE>}")
        if win32api.GetLastError() == 0:
            break
        win32api.CloseHandle(mutex)
        if first:
            print "Another instance of %s running, please wait for completion" % main_path
            first = False
        time.sleep(1)

Spiegazione

Il codice tenta di creare un mutex con il nome derivato dal percorso completo dello script. Usiamo le barre in avanti per evitare potenziali confusioni con il vero file system.

vantaggi

  • Non sono necessari identificatori di configurazione o "magici", utilizzali in tutti gli script diversi necessari.
  • Nessun file obsoleto lasciato in giro, il mutex muore con te.
  • Stampa un messaggio utile durante l'attesa

3

La soluzione migliore per questo su Windows è usare i mutex come suggerito da @zgoda.

import win32event
import win32api
from winerror import ERROR_ALREADY_EXISTS

mutex = win32event.CreateMutex(None, False, 'name')
last_error = win32api.GetLastError()

if last_error == ERROR_ALREADY_EXISTS:
   print("App instance already running")

Alcune risposte usano fctnl(incluso anche nel pacchetto @sorin tendo) che non è disponibile su Windows e se provi a congelare la tua app python usando un pacchetto come pyinstallerche fa importazioni statiche, genera un errore.

Inoltre, l'utilizzo del metodo del file di blocco, crea un read-onlyproblema con i file di database (riscontrato con sqlite3).


2

Sto postando questo come risposta perché sono un nuovo utente e Stack Overflow non mi consente ancora di votare.

La soluzione di Sorin Sbarnea funziona per me su OS X, Linux e Windows e ne sono grato.

Tuttavia, tempfile.gettempdir () si comporta in un modo in OS X e Windows e in un altro in altri alcuni / molti / tutti (?) * Nix (ignorando il fatto che OS X è anche Unix!). La differenza è importante per questo codice.

OS X e Windows hanno directory temporanee specifiche dell'utente, quindi un file temporaneo creato da un utente non è visibile a un altro utente. Al contrario, in molte versioni di * nix (ho testato Ubuntu 9, RHEL 5, OpenSolaris 2008 e FreeBSD 8), la directory temporanea è / tmp per tutti gli utenti.

Ciò significa che quando il file di blocco viene creato su una macchina multiutente, viene creato in / tmp e solo l'utente che crea il file di blocco la prima volta sarà in grado di eseguire l'applicazione.

Una possibile soluzione è incorporare il nome utente corrente nel nome del file di blocco.

Vale la pena notare che la soluzione dell'OP di afferrare una porta si comporterà anche male su una macchina multiutente.


Per alcuni lettori (ad esempio io) il comportamento desiderato è che solo una copia può essere eseguita per un periodo, indipendentemente dal numero di utenti coinvolti. Quindi le directory tmp per utente sono interrotte, mentre il shared / tmp o il blocco della porta mostrano il comportamento desiderato.
Jonathan Hartley


1

Continuo a sospettare che dovrebbe esserci una buona soluzione POSIXy che utilizzi i gruppi di processi, senza dover premere il file system, ma non riesco a risolverlo del tutto. Qualcosa di simile a:

All'avvio, il tuo processo invia un "kill -0" a tutti i processi in un particolare gruppo. Se esistono tali processi, esso esce. Quindi si unisce al gruppo. Nessun altro processo utilizza quel gruppo.

Tuttavia, questo ha una condizione di gara: più processi potrebbero farlo esattamente nello stesso momento e tutti finiscono per unirsi al gruppo e funzionare simultaneamente. Quando avrai aggiunto una sorta di mutex per renderlo impermeabile, non avrai più bisogno dei gruppi di processo.

Questo potrebbe essere accettabile se il tuo processo viene avviato solo da cron, una volta ogni minuto o ogni ora, ma mi rende un po 'nervoso il fatto che potrebbe andare storto proprio il giorno in cui non lo desideri.

Immagino che dopo tutto questa non sia una buona soluzione, a meno che qualcuno non possa migliorarla?


1

Mi sono imbattuto in questo problema esatto la scorsa settimana e, sebbene abbia trovato alcune buone soluzioni, ho deciso di creare un pacchetto Python molto semplice e pulito e l'ho caricato su PyPI. Si differenzia da tendo in quanto può bloccare qualsiasi nome di risorsa stringa. Anche se potresti sicuramente bloccare__file__ per ottenere lo stesso effetto.

Installa con: pip install quicklock

Usarlo è estremamente semplice:

[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> # Let's create a lock so that only one instance of a script will run
...
>>> singleton('hello world')
>>>
>>> # Let's try to do that again, this should fail
...
>>> singleton('hello world')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nate/live/gallery/env/lib/python2.7/site-packages/quicklock/quicklock.py", line 47, in singleton
    raise RuntimeError('Resource <{}> is currently locked by <Process {}: "{}">'.format(resource, other_process.pid, other_process.name()))
RuntimeError: Resource <hello world> is currently locked by <Process 24801: "python">
>>>
>>> # But if we quit this process, we release the lock automatically
...
>>> ^D
[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> singleton('hello world')
>>>
>>> # No exception was thrown, we own 'hello world'!

Dai un'occhiata: https://pypi.python.org/pypi/quicklock


1
L'ho appena installato tramite "pip install quicklock", ma quando provo a usarlo tramite "da quicklock import singleton" ottengo un'eccezione: "ImportError: can't import name 'singleton'". Questo è su un Mac.
grayaii

Si scopre che Quicklock non funziona con Python 3. Questo è il motivo per cui non funzionava.
grayaii

Sì, scusa, non era affatto a prova di futuro. Sarò lieto di un contributo per farlo funzionare!
Nate Ferrero

1

Partendo dalla risposta di Roberto Rosario, ho ideato la seguente funzione:

SOCKET = None
def run_single_instance(uniq_name):
    try:
        import socket
        global SOCKET
        SOCKET = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        ## Create an abstract socket, by prefixing it with null.
        # this relies on a feature only in linux, when current process quits, the
        # socket will be deleted.
        SOCKET.bind('\0' + uniq_name)
        return True
    except socket.error as e:
        return False

Dobbiamo definire la SOCKETvariabile globale poiché verrà raccolta solo quando l'intero processo termina. Se dichiariamo una variabile locale nella funzione, questa uscirà dall'ambito dopo che la funzione è uscita, quindi il socket verrà eliminato.

Tutto il merito va a Roberto Rosario, poiché mi limito a chiarire ed elaborare il suo codice. E questo codice funzionerà solo su Linux, come spiega il seguente testo citato da https://troydhanson.github.io/network/Unix_domain_sockets.html :

Linux ha una caratteristica speciale: se il percorso per un socket di dominio UNIX inizia con un byte nullo \ 0, il suo nome non viene mappato nel filesystem. Quindi non entrerà in collisione con altri nomi nel filesystem. Inoltre, quando un server chiude il suo socket di ascolto del dominio UNIX nello spazio dei nomi astratto, il suo file viene eliminato; con i normali socket di dominio UNIX, il file persiste dopo che il server lo chiude.


0

esempio di linux

Questo metodo si basa sulla creazione di un file temporaneo automaticamente eliminato dopo la chiusura dell'applicazione. all'avvio del programma verifichiamo l'esistenza del file; se il file esiste (c'è un'esecuzione in sospeso), il programma viene chiuso; altrimenti crea il file e prosegue l'esecuzione del programma.

from tempfile import *
import time
import os
import sys


f = NamedTemporaryFile( prefix='lock01_', delete=True) if not [f  for f in     os.listdir('/tmp') if f.find('lock01_')!=-1] else sys.exit()

YOUR CODE COMES HERE

1
Benvenuto in Stack Overflow! Sebbene questa risposta possa essere corretta, aggiungi qualche spiegazione. L'impartire la logica sottostante è più importante del semplice dare il codice, perché aiuta l'OP e altri lettori a risolvere da soli questo e altri problemi simili.
CodeMouse92

Questo thread è sicuro? Sembra che il controllo e la creazione del file temporaneo non siano atomici ...
coppit

0

Su un sistema Linux si potrebbe anche chiedere pgrep -ail numero di istanze, lo script si trova nella lista dei processi (l'opzione -a rivela l'intera stringa della riga di comando). Per esempio

import os
import sys
import subprocess

procOut = subprocess.check_output( "/bin/pgrep -u $UID -a python", shell=True, 
                                   executable="/bin/bash", universal_newlines=True)

if procOut.count( os.path.basename(__file__)) > 1 :        
    sys.exit( ("found another instance of >{}<, quitting."
              ).format( os.path.basename(__file__)))

Rimuovere -u $UIDse la restrizione deve essere applicata a tutti gli utenti. Dichiarazione di non responsabilità: a) si presume che il nome (di base) dello script sia univoco, b) potrebbero esserci condizioni di gara.


-1
import sys,os

# start program
try:  # (1)
    os.unlink('lock')  # (2)
    fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (3)  
except: 
    try: fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (4) 
    except:  
        print "Another Program running !.."  # (5)
        sys.exit()  

# your program  ...
# ...

# exit program
try: os.close(fd)  # (6)
except: pass
try: os.unlink('lock')  
except: pass
sys.exit()  

2
Benvenuto in Stack Overflow! Sebbene questo blocco di codice possa rispondere alla domanda, sarebbe meglio se tu potessi fornire una piccola spiegazione del motivo per cui lo fa. Si prega di modificare la risposta per includere una tale descrizione.
Artjom B.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.