web-dev-qa-db-fra.com

Comment créer une application à instance unique en C ou C ++

Quelle serait votre suggestion pour créer une application à instance unique, afin qu'un seul processus soit autorisé à s'exécuter à la fois? Verrouillage de fichier, mutex ou quoi?

50
whoi

Un bon moyen est:

#include <sys/file.h>
#include <errno.h>

int pid_file = open("/var/run/whatever.pid", O_CREAT | O_RDWR, 0666);
int rc = flock(pid_file, LOCK_EX | LOCK_NB);
if(rc) {
    if(EWOULDBLOCK == errno)
        ; // another instance is running
}
else {
    // this is the first instance
}

Notez que le verrouillage vous permet d'ignorer les fichiers PID périmés (c'est-à-dire que vous n'avez pas à les supprimer). Lorsque l'application se termine pour une raison quelconque, le système d'exploitation libère le verrouillage de fichier pour vous.

Les fichiers PID ne sont pas terriblement utiles car ils peuvent être périmés (le fichier existe mais le processus n'existe pas). Par conséquent, l'exécutable de l'application lui-même peut être verrouillé au lieu de créer et de verrouiller un fichier pid.

Une méthode plus avancée consiste à créer et à lier un socket de domaine Unix en utilisant un nom de socket prédéfini. La liaison réussit pour la première instance de votre application. Encore une fois, le système d'exploitation délie le socket lorsque l'application se termine pour une raison quelconque. Lorsque bind() échoue, une autre instance de l'application peut connect() et utiliser ce socket pour passer ses arguments de ligne de commande à la première instance.

59
Maxim Egorushkin

Voici une solution en C++. Il utilise la recommandation de socket de Maxim. J'aime mieux cette solution que la solution de verrouillage basée sur un fichier, car celle basée sur un fichier échoue si le processus se bloque et ne supprime pas le fichier de verrouillage. Un autre utilisateur ne pourra pas supprimer le fichier et le verrouiller. Les sockets sont automatiquement supprimées à la fin du processus.

Usage:

int main()
{
   SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
   if (!singleton())
   {
     cerr << "process running already. See " << singleton.GetLockFileName() << endl;
     return 1;
   }
   ... rest of the app
}

Code:

#include <netinet/in.h>

class SingletonProcess
{
public:
    SingletonProcess(uint16_t port0)
            : socket_fd(-1)
              , rc(1)
              , port(port0)
    {
    }

    ~SingletonProcess()
    {
        if (socket_fd != -1)
        {
            close(socket_fd);
        }
    }

    bool operator()()
    {
        if (socket_fd == -1 || rc)
        {
            socket_fd = -1;
            rc = 1;

            if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
            {
                throw std::runtime_error(std::string("Could not create socket: ") +  strerror(errno));
            }
            else
            {
                struct sockaddr_in name;
                name.sin_family = AF_INET;
                name.sin_port = htons (port);
                name.sin_addr.s_addr = htonl (INADDR_ANY);
                rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
            }
        }
        return (socket_fd != -1 && rc == 0);
    }

    std::string GetLockFileName()
    {
        return "port " + std::to_string(port);
    }

private:
    int socket_fd = -1;
    int rc;
    uint16_t port;
};
13
Mark Lakata

Évitez le verrouillage basé sur des fichiers

Il est toujours bon d'éviter un mécanisme de verrouillage basé sur un fichier pour implémenter l'instance singleton d'une application. L'utilisateur peut toujours renommer le fichier de verrouillage sous un nom différent et réexécuter l'application comme suit:

mv lockfile.pid lockfile1.pid

lockfile.pid est le fichier de verrouillage sur la base duquel il est vérifié l'existence avant d'exécuter l'application.

Ainsi, il est toujours préférable d'utiliser un schéma de verrouillage sur un objet directement visible uniquement pour le noyau. Donc, tout ce qui a à voir avec un système de fichiers n'est pas fiable.

La meilleure option serait donc de se lier à une socket inet. Notez que les sockets de domaine Unix résident dans le système de fichiers et ne sont pas fiables.

Alternativement, vous pouvez également le faire en utilisant DBUS.

5
Binoy

Pour Windows, un objet noyau nommé (par exemple, CreateEvent, CreateMutex). Pour Unix, un fichier pid - créez un fichier et écrivez-y votre ID de processus.

5
Erik

Cela ne semble pas être mentionné - il est possible de créer un mutex dans la mémoire partagée mais il doit être marqué comme partagé par des attributs (non testé):

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_t *mutex = shmat(SHARED_MEMORY_ID, NULL, 0);
pthread_mutex_init(mutex, &attr);

Il existe également des sémaphores de mémoire partagée (mais je n'ai pas trouvé comment en verrouiller un):

int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);
3
Maciej Piechotka

Vous pouvez créer un socket AF_UNIX "namespace anonyme". Ceci est entièrement spécifique à Linux, mais présente l'avantage qu'aucun système de fichiers ne doit réellement exister.

Lisez la page de manuel pour unix (7) pour plus d'informations.

2
MarkR

Cela dépendra du problème que vous souhaitez éviter en forçant votre application à n'avoir qu'une seule instance et la portée sur laquelle vous considérez les instances.

Pour un démon - la manière habituelle est d'avoir un /var/run/app.pid fichier.

Pour les applications utilisateur, j'ai eu plus de problèmes avec les applications qui m'ont empêché de les exécuter deux fois qu'avec le fait de pouvoir exécuter deux fois une application qui n'aurait pas dû être exécutée ainsi. Ainsi, la réponse sur "pourquoi et sur quelle portée" est très importante et apportera probablement une réponse spécifique sur le pourquoi et la portée prévue.

2
AProgrammer

Personne ne l'a mentionné, mais sem_open() crée un véritable sémaphore nommé sous les systèmes d'exploitation compatibles POSIX modernes. Si vous donnez à un sémaphore une valeur initiale de 1, il devient un mutex (tant qu'il n'est strictement libéré que si un verrou a été obtenu avec succès).

Avec plusieurs objets basés sur sem_open(), vous pouvez créer tous les objets nommés Windows communs équivalents - mutex nommés, sémaphores nommés et événements nommés. Les événements nommés avec "manual" défini sur true sont un peu plus difficiles à émuler (cela nécessite quatre objets sémaphores pour émuler correctement CreateEvent(), SetEvent() et ResetEvent()) . Quoi qu'il en soit, je m'égare.

Il existe également une mémoire partagée nommée. Vous pouvez initialiser un mutex pthread avec l'attribut "processus partagé" dans la mémoire partagée nommée, puis tous les processus peuvent accéder en toute sécurité à cet objet mutex après avoir ouvert un handle vers la mémoire partagée avec shm_open()/mmap(). sem_open() est plus facile s'il est disponible pour votre plate-forme (si ce n'est pas le cas, ce devrait être pour raison).

Quelle que soit la méthode que vous utilisez, pour tester une seule instance de votre application, utilisez la variante trylock() de la fonction d'attente (par exemple sem_trywait() ). Si le processus est le seul en cours d'exécution, il verrouillera avec succès le mutex. Si ce n'est pas le cas, il échouera immédiatement.

N'oubliez pas de déverrouiller et de fermer le mutex à la sortie de l'application.

2
CubicleSoft

Je viens d'en écrire un et de le tester.

#define PID_FILE "/tmp/pidfile"
static void create_pidfile(void) {
    int fd = open(PID_FILE, O_RDWR | O_CREAT | O_EXCL, 0);

    close(fd);
}

int main(void) {
    int fd = open(PID_FILE, O_RDONLY);
    if (fd > 0) {
        close(fd);
        return 0;
    }

    // make sure only one instance is running
    create_pidfile();
}
0
Akagi201

Voici une solution basée sur sem_open

/*
*compile with :
*gcc single.c -o single -pthread
*/

/*
 * run multiple instance on 'single', and check the behavior
 */
#include <stdio.h>
#include <fcntl.h>    
#include <sys/stat.h>        
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>

#define SEM_NAME "/mysem_911"

int main()
{
  sem_t *sem;
  int rc; 

  sem = sem_open(SEM_NAME, O_CREAT, S_IRWXU, 1); 
  if(sem==SEM_FAILED){
    printf("sem_open: failed errno:%d\n", errno);
  }

  rc=sem_trywait(sem);

  if(rc == 0){ 
    printf("Obtained lock !!!\n");
    sleep(10);
    //sem_post(sem);
    sem_unlink(SEM_NAME);
  }else{
    printf("Lock not obtained\n");
  }
}

Un des commentaires sur une réponse différente dit "J'ai trouvé que sem_open () manquait plutôt". Je ne suis pas sûr des détails de ce qui manque

0
Anil Kainikara

Exécutez simplement ce code sur un thread séparé:

void lock() {
  while(1) {
    ofstream closer("myapplock.locker", ios::trunc);
    closer << "locked";
    closer.close();
  }
}

Exécutez ceci comme votre code principal:

int main() {
  ifstream reader("myapplock.locker");
  string s;
  reader >> s;
  if (s != "locked") {
  //your code
  }
  return 0;
}
0
ndrewxie

Sur la base des conseils de réponse de maxim voici ma solution POSIX d'un démon à double rôle (c'est-à-dire une application unique qui peut agir comme démon et comme client communiquant avec ce démon). Ce schéma a l'avantage de fournir une solution élégante du problème lorsque l'instance démarrée en premier doit être le démon et que toutes les exécutions suivantes doivent simplement charger le travail sur ce démon. C'est un exemple complet mais il manque beaucoup de choses qu'un vrai démon devrait faire (par exemple en utilisant syslog pour la journalisation et fork pour se mettre correctement en arrière-plan , abandon des privilèges etc.), mais il est déjà assez long et fonctionne pleinement tel quel. Jusqu'à présent, je n'ai testé cela que sur Linux mais IIRC devrait être entièrement compatible POSIX.

Dans l'exemple, les clients peuvent envoyer des entiers passés en tant que premier argument de ligne de commande et analysés par atoi via le socket au démon qui l'imprime dans stdout. Avec ce type de sockets, il est également possible de transférer des tableaux, des structures et même des descripteurs de fichiers (voir man 7 unix).

#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_NAME "/tmp/exampled"

static int socket_fd = -1;
static bool isdaemon = false;
static bool run = true;

/* returns
 *   -1 on errors
 *    0 on successful server bindings
 *   1 on successful client connects
 */
int singleton_connect(const char *name) {
    int len, tmpd;
    struct sockaddr_un addr = {0};

    if ((tmpd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        printf("Could not create socket: '%s'.\n", strerror(errno));
        return -1;
    }

    /* fill in socket address structure */
    addr.Sun_family = AF_UNIX;
    strcpy(addr.Sun_path, name);
    len = offsetof(struct sockaddr_un, Sun_path) + strlen(name);

    int ret;
    unsigned int retries = 1;
    do {
        /* bind the name to the descriptor */
        ret = bind(tmpd, (struct sockaddr *)&addr, len);
        /* if this succeeds there was no daemon before */
        if (ret == 0) {
            socket_fd = tmpd;
            isdaemon = true;
            return 0;
        } else {
            if (errno == EADDRINUSE) {
                ret = connect(tmpd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
                if (ret != 0) {
                    if (errno == ECONNREFUSED) {
                        printf("Could not connect to socket - assuming daemon died.\n");
                        unlink(name);
                        continue;
                    }
                    printf("Could not connect to socket: '%s'.\n", strerror(errno));
                    continue;
                }
                printf("Daemon is already running.\n");
                socket_fd = tmpd;
                return 1;
            }
            printf("Could not bind to socket: '%s'.\n", strerror(errno));
            continue;
        }
    } while (retries-- > 0);

    printf("Could neither connect to an existing daemon nor become one.\n");
    close(tmpd);
    return -1;
}

static void cleanup(void) {
    if (socket_fd >= 0) {
        if (isdaemon) {
            if (unlink(SOCKET_NAME) < 0)
                printf("Could not remove FIFO.\n");
        } else
            close(socket_fd);
    }
}

static void handler(int sig) {
    run = false;
}

int main(int argc, char **argv) {
    switch (singleton_connect(SOCKET_NAME)) {
        case 0: { /* Daemon */

            struct sigaction sa;
            sa.sa_handler = &handler;
            sigemptyset(&sa.sa_mask);
            if (sigaction(SIGINT, &sa, NULL) != 0 || sigaction(SIGQUIT, &sa, NULL) != 0 || sigaction(SIGTERM, &sa, NULL) != 0) {
                printf("Could not set up signal handlers!\n");
                cleanup();
                return EXIT_FAILURE;
            }

            struct msghdr msg = {0};
            struct iovec iovec;
            int client_arg;
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;

            while (run) {
                int ret = recvmsg(socket_fd, &msg, MSG_DONTWAIT);
                if (ret != sizeof(client_arg)) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        printf("Error while accessing socket: %s\n", strerror(errno));
                        exit(1);
                    }
                    printf("No further client_args in socket.\n");
                } else {
                    printf("received client_arg=%d\n", client_arg);
                }

                /* do daemon stuff */
                sleep(1);
            }
            printf("Dropped out of daemon loop. Shutting down.\n");
            cleanup();
            return EXIT_FAILURE;
        }
        case 1: { /* Client */
            if (argc < 2) {
                printf("Usage: %s <int>\n", argv[0]);
                return EXIT_FAILURE;
            }
            struct iovec iovec;
            struct msghdr msg = {0};
            int client_arg = atoi(argv[1]);
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;
            int ret = sendmsg(socket_fd, &msg, 0);
            if (ret != sizeof(client_arg)) {
                if (ret < 0)
                    printf("Could not send device address to daemon: '%s'!\n", strerror(errno));
                else
                    printf("Could not send device address to daemon completely!\n");
                cleanup();
                return EXIT_FAILURE;
            }
            printf("Sent client_arg (%d) to daemon.\n", client_arg);
            break;
        }
        default:
            cleanup();
            return EXIT_FAILURE;
    }

    cleanup();
    return EXIT_SUCCESS;
}
0
stefanct