Как создать приложение с одним экземпляром на C или C ++ - PullRequest
50 голосов
/ 17 марта 2011

Что бы вы посоветовали для создания приложения с одним экземпляром, чтобы одновременно разрешалось запускать только один процесс?Блокировка файла, мьютекс или что?

Ответы [ 12 ]

59 голосов
/ 17 марта 2011

Хороший способ:

#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
}

Обратите внимание, что блокировка позволяет игнорировать устаревшие pid-файлы (т.е. вам не нужно их удалять).Когда приложение по какой-либо причине завершает работу, ОС снимает блокировку файла.

Файлы PID не очень полезны, потому что они могут быть устаревшими (файл существует, но процесс не выполняется).Следовательно, сам исполняемый файл приложения может быть заблокирован вместо создания и блокировки файла pid.

Более продвинутый метод заключается в создании и привязке сокета домена unix с использованием предварительно определенного имени сокета.Привязка успешна для первого экземпляра вашего приложения.Опять же, ОС отсоединяет сокет, когда приложение завершается по любой причине.При сбое bind() другой экземпляр приложения может connect() и использовать этот сокет для передачи аргументов командной строки в первый экземпляр.

13 голосов
/ 30 октября 2014

Вот решение на C ++. Он использует рекомендации сокета Максима. Мне нравится это решение лучше, чем решение для блокировки на основе файлов, потому что на основе файлов происходит сбой, если процесс завершается сбоем и не удаляет файл блокировки. Другой пользователь не сможет удалить файл и заблокировать его. Сокеты автоматически удаляются при выходе из процесса.

Использование:

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
}

Код:

#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;
};
5 голосов
/ 19 августа 2014

Избегать блокировки на основе файлов

Всегда полезно избегать механизма блокировки на основе файлов для реализации одноэлементного экземпляра приложения. Пользователь всегда может переименовать файл блокировки на другое имя и снова запустить приложение следующим образом:

mv lockfile.pid lockfile1.pid

Где lockfile.pid - файл блокировки, на основе которого проверяется наличие перед запуском приложения.

Таким образом, всегда предпочтительнее использовать схему блокировки для объекта, видимого только ядру. Поэтому все, что связано с файловой системой, ненадежно.

Так что лучшим вариантом будет привязка к inet-сокету. Обратите внимание, что доменные сокеты Unix находятся в файловой системе и не являются надежными.

Кроме того, вы также можете сделать это с помощью DBUS.

5 голосов
/ 17 марта 2011

Для окон - именованный объект ядра (например, CreateEvent, CreateMutex).Для unix pid-файл - создайте файл и запишите в него свой идентификатор процесса.

3 голосов
/ 16 сентября 2011

Это, кажется, не упоминается - возможно создание мьютекса в разделяемой памяти, но его нужно пометить как разделяемое атрибутами (не проверено):

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

Также есть семафоры совместно используемой памяти(но я не смог выяснить, как его заблокировать):

int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);
2 голосов
/ 03 ноября 2013

Никто не упомянул об этом, но sem_open() создает настоящий именованный семафор под современными POSIX-совместимыми ОС.Если вы присвоите семафору начальное значение 1, он станет мьютексом (если он строго освобожден, только если блокировка была успешно получена).

С несколькими объектами на основе sem_open() вы можете создатьвсе общие эквивалентные именованные объекты Windows - именованные мьютексы, именованные семафоры и именованные события.Именованные события с «ручным» значением true немного сложнее эмулировать (для правильной эмуляции CreateEvent(), SetEvent() и ResetEvent() требуется четыре объекта семафора).Во всяком случае, я отвлекся.

В качестве альтернативы, там есть общая память.Вы можете инициализировать мьютекс pthread с атрибутом «общий процесс» в именованной общей памяти, и тогда все процессы смогут безопасно получить доступ к этому объекту мьютекса после открытия дескриптора общей памяти с помощью shm_open() / mmap().sem_open() проще, если он доступен для вашей платформы (если это не так, это должно быть ради здравомыслия).

Независимо от используемого вами метода, для тестирования одного экземпляра вашего приложения,используйте trylock() вариант функции ожидания (например, sem_trywait()).Если процесс запущен только один раз, он успешно заблокирует мьютекс.Если это не так, он немедленно завершится ошибкой.

Не забудьте разблокировать и закрыть мьютекс при выходе из приложения.

2 голосов
/ 17 марта 2011

Вы можете создать сокет AF_UNIX «анонимного пространства имен».Это полностью зависит от Linux, но имеет то преимущество, что файловая система фактически не должна существовать.

Для получения дополнительной информации прочитайте справочную страницу по unix (7).

2 голосов
/ 17 марта 2011

Это будет зависеть от того, какую проблему вы хотите избежать, вынуждая ваше приложение иметь только один экземпляр и область, в которой вы рассматриваете экземпляры.

Для демона - обычно есть файл /var/run/app.pid.

Что касается пользовательских приложений, у меня было больше проблем с приложениями, которые мешали мне запускать их дважды, чем с возможностью запуска дважды приложения, которое не должно было выполняться так. Поэтому ответ на вопрос «почему и в каком объеме» очень важен и, вероятно, принесет конкретный ответ о причинах и предполагаемом объеме.

0 голосов
/ 18 мая 2018

Вот решение, основанное на 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");
  }
}

Один из комментариев к другому ответу гласит: «Я обнаружил, что sem_open () довольно не хватает». Я не уверен в специфике того, чего не хватает

0 голосов
/ 18 марта 2017

Просто запустите этот код в отдельном потоке:

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

Запустите это как ваш основной код:

int main() {
  ifstream reader("myapplock.locker");
  string s;
  reader >> s;
  if (s != "locked") {
  //your code
  }
  return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...