web-dev-qa-db-fra.com

Python: instance unique du programme

Existe-t-il un moyen Pythonic de n’exécuter qu’une seule instance d’un programme? 

La seule solution raisonnable que j'ai proposée consiste à l'exécuter en tant que serveur sur un port, puis le second programme essayant de se lier au même port - échoue. Mais ce n'est pas vraiment une bonne idée, peut-être qu'il y a quelque chose de plus léger que ça? 

(Tenez compte du fait que le programme échouera parfois, c’est-à-dire segfault - donc des choses comme "fichier verrouillé" ne fonctionneront pas)

Mettre à jour: les solutions proposées sont beaucoup plus complexes et moins fiables que le simple fait d’avoir un port occupé par un serveur inexistant, je devrais donc aller avec celui-ci.

98
Slava V

Le code suivant devrait faire le travail, il est multi-plateforme et fonctionne sur Python 2.4-3.2. Je l'ai testé sous Windows, OS X et Linux.

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

La dernière version du code est disponible singleton.py . S'il vous plaît fichier bugs ici .

Vous pouvez installer tend en utilisant l’une des méthodes suivantes:

87
sorin

Simple, multiplate-forme solution, trouvée dans une autre question de zgoda :

import fcntl, sys
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(0)

Un peu comme la suggestion de S.Lott, mais avec le code.

33
Slava V

Ce code est spécifique à Linux. Il utilise des sockets de domaine UNIX «abstraits», mais il est simple et ne laisse pas de fichiers de verrouillage obsolètes. Je le préfère à la solution ci-dessus car il ne nécessite pas de port TCP spécialement réservé. 

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 chaîne unique postconnect_gateway_notify_lock peut être modifiée pour autoriser plusieurs programmes nécessitant l'application d'une seule instance.

25
Roberto Rosario

Je ne sais pas si c'est assez Pythonic, mais dans le monde Java, l'écoute sur un port défini est une solution assez largement utilisée, car elle fonctionne sur toutes les grandes plates-formes et ne pose aucun problème de blocage des programmes.

Un autre avantage de l'écoute d'un port est que vous pouvez envoyer une commande à l'instance en cours d'exécution. Par exemple, lorsque les utilisateurs démarrent le programme une seconde fois, vous pouvez envoyer une commande à l'instance en cours pour lui demander d'ouvrir une autre fenêtre (c'est ce que fait Firefox, par exemple. Je ne sais pas s'ils utilisent les ports TCP ou des tuyaux nommés ou quelque chose comme ça, ').

19
Joachim Sauer

Jamais écrit en python auparavant, mais voici ce que je viens d'implémenter dans mycheckpoint, pour éviter qu'il ne soit démarré deux fois ou plus par 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()

J'ai trouvé la suggestion de Slava-N après l'avoir publiée dans un autre numéro (http://stackoverflow.com/questions/2959474). Celui-ci est appelé en tant que fonction, verrouille le fichier de scripts en cours d'exécution (pas un fichier pid) et le maintient jusqu'à la fin du script (normal ou erreur).

9
M.D. Klapwijk

Utilisez un fichier pid. Vous avez un emplacement connu, "/ path/to/pidfile" et au démarrage vous faites quelque chose comme ceci (partiellement pseudocode parce que je suis pré-café et que je ne veux pas travailler aussi dur):

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())

Donc, en d'autres termes, vous vérifiez si un pidfile existe; sinon, écrivez votre pid dans ce fichier. Si le fichier pid existe, vérifiez si le pid est le pid d'un processus en cours d'exécution; Si c'est le cas, alors vous avez un autre processus en cours, alors arrêtez-vous. Si ce n'est pas le cas, le processus précédent s'est bloqué. Consignez-le, puis écrivez votre propre pid dans le fichier à la place de l'ancien. Alors continue.

8
Charlie Martin

Vous avez déjà trouvé une réponse à une question similaire dans un autre fil de discussion. Par souci de complétude, voyez comment obtenir le même résultat sous Windows uning nommé mutex.

http://code.activestate.com/recipes/474070/

6
zgoda

Cela peut fonctionner.

  1. Essayez de créer un fichier PID dans un emplacement connu. Si vous échouez, le fichier est verrouillé, vous avez terminé.

  2. Lorsque vous avez terminé normalement, fermez et supprimez le fichier PID afin que quelqu'un d'autre puisse l'écraser.

Vous pouvez envelopper votre programme dans un script Shell qui supprime le fichier PID même si votre programme se bloque.

Vous pouvez également utiliser le fichier PID pour tuer le programme s'il se bloque.

4
S.Lott

Pour tous ceux qui utilisent wxPython pour leur application, vous pouvez utiliser la fonction wx.SingleInstanceCheckerdocumentée ici .

Personnellement, j'utilise une sous-classe de wx.App qui utilise wx.SingleInstanceChecker et renvoie False de OnInit() s'il existe une instance existante de l'application qui s'exécute déjà de la manière suivante:

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

Il s'agit d'un simple remplacement instantané de wx.App qui interdit plusieurs instances. Pour l'utiliser, il suffit de remplacer wx.App par SingleApp dans votre code, de la manière suivante:

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

L'utilisation d'un fichier de verrouillage est une approche assez courante sous Unix. Si cela se bloque, vous devez nettoyer manuellement. Vous pouvez stocker le PID dans le fichier et vérifier au démarrage si un processus avec ce PID est en cours, en remplaçant le fichier de verrouillage sinon. (Cependant, vous avez également besoin d'un verrou autour du fichier read-file-check-pid-rewrite-). Vous trouverez ce dont vous avez besoin pour obtenir et vérifier pid dans le paquet os -. La manière courante de vérifier s’il existe un processus avec un pid donné est de lui envoyer un signal non fatal.

D'autres alternatives pourraient être combinées avec des sémaphores flock ou posix.

L’ouverture d’une prise réseau, comme le proposait Saua, serait probablement la plus facile et la plus portable.

3
Rolf Rander

J'utilise single_process sur mon gentoo;

pip install single_process

Exemple

from single_process import single_process

@single_process
def main():
    print 1

if __== "__main__":
    main()   

se référer: https://pypi.python.org/pypi/single_process/1.0

2
gkiwi

Je poste ceci comme réponse car je suis un nouvel utilisateur et Stack Overflow ne me permet pas encore de voter.

La solution de Sorin Sbarnea fonctionne pour moi sous OS X, Linux et Windows, et j'en suis reconnaissant.

Cependant, tempfile.gettempdir () se comporte d’une manière sous OS X et Windows et d’une autre sous un autre/plusieurs/tous (?) * Nixes (en ignorant le fait que OS X est aussi Unix!). La différence est importante pour ce code. 

OS X et Windows ont des répertoires temporaires spécifiques à l'utilisateur. Ainsi, un fichier temporaire créé par un utilisateur n'est pas visible par un autre utilisateur. En revanche, dans de nombreuses versions de * nix (j'ai testé Ubuntu 9, RHEL 5, OpenSolaris 2008 et FreeBSD 8), le répertoire temporaire est/tmp pour tous les utilisateurs.

Cela signifie que lorsque le fichier lockfile est créé sur un ordinateur multi-utilisateur, il est créé dans/tmp et seul l'utilisateur qui crée le fichier lockfile pour la première fois pourra exécuter l'application.

Une solution possible consiste à incorporer le nom d'utilisateur actuel dans le nom du fichier de verrouillage.

Il convient de noter que la solution de l'OP consistant à saisir un port entraînera également un comportement déplacé sur une machine multi-utilisateur.

2
Philip Semanchuk

Voici mon éventuelle solution Windows uniquement. Mettez ce qui suit dans un module, appelé peut-être 'onlyone.py', ou autre chose. Incluez ce module directement dans votre fichier de 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)

Explication

Le code tente de créer un mutex avec un nom dérivé du chemin complet du script. Nous utilisons des barres obliques pour éviter toute confusion possible avec le système de fichiers réel.

Avantages

  • Aucune configuration ni identifiant "magique" n'est nécessaire, utilisez-le dans autant de scripts différents que nécessaire.
  • Pas de fichiers périmés, le mutex meurt avec vous.
  • Imprime un message utile en attendant
2
Keeely

exemple linux

Cette méthode est basée sur la création d'un fichier temporaire automatiquement supprimé après la fermeture de l'application . Au lancement du programme, nous vérifions l'existence du fichier; Si le fichier existe (il y a une exécution en attente), le programme est fermé ; sinon, il crée le fichier et continue l'exécution du programme.

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
kerwal

La meilleure solution pour cela sur Windows consiste à utiliser des mutex comme suggéré par @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")

Certaines réponses utilisent fctnl (inclus également dans le paquet @sorin tendo) qui n'est pas disponible sous Windows et si vous essayez de figer votre application python en utilisant un paquetage comme pyinstaller qui effectue des importations statiques, une erreur est générée.

En outre, à l'aide de la méthode du fichier verrou, crée un problème read-only avec les fichiers de base de données (rencontré avec sqlite3).

0
Chuck G

J'ai rencontré ce problème exactement la semaine dernière et, bien que j'aie trouvé de bonnes solutions, j'ai décidé de créer un paquet python très simple et propre et de le télécharger sur PyPI. Il diffère de tendo en ce qu'il peut verrouiller n'importe quel nom de ressource de chaîne. Bien que vous puissiez certainement verrouiller __file__ pour obtenir le même effet.

Installer avec: pip install quicklock

Son utilisation est extrêmement simple:

[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'!

Jetez un coup d'oeil: https://pypi.python.org/pypi/quicklock

0
Nate Ferrero

Sur un système Linux, vous pouvez également demander pgrep -a pour le nombre d'instances, le script Est trouvé dans la liste des processus (l'option -a révèle la chaîne de caractères Full.). Par exemple.

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__)))

Supprimez -u $UID si la restriction doit s’appliquer à all users. Avertissement: a) il est supposé que le nom (de base) du script est unique, b) il peut y avoir des conditions de concurrence.

0
user71769

Je continue de penser qu'il devrait exister une bonne solution POSIXy utilisant des groupes de processus, sans avoir à utiliser le système de fichiers, mais je ne peux pas tout comprendre. Quelque chose comme:

Au démarrage, votre processus envoie un message "kill -0" à tous les processus d'un groupe particulier. Si de tels processus existent, il existe. Ensuite, il rejoint le groupe. Aucun autre processus n'utilise ce groupe.

Cependant, il existe une condition de concurrence critique: plusieurs processus peuvent le faire exactement au même moment et tous finissent par rejoindre le groupe et s'exécuter simultanément. Au moment où vous avez ajouté une sorte de mutex pour le rendre étanche, vous n'avez plus besoin des groupes de processus.

Cela pourrait être acceptable si votre processus ne démarre que par cron, une fois par minute ou toutes les heures, mais je suis un peu inquiet que cela ne se passe pas bien le jour où vous ne le souhaitez pas.

Je suppose que ce n’est pas une très bonne solution après tout, à moins que quelqu'un ne puisse l’améliorer?

0
Jonathan Hartley