Один экземпляр программы C++ с использованием boost::interprocess

У меня есть консольное приложение, которое я пытаюсь запустить только один раз за раз. Для этого я использовал библиотеку межпроцессного взаимодействия shared_memory_object. См. фрагмент кода ниже,

  boost::scoped_ptr<shared_memory_object> sharedMem;

  try
  {
     sharedMem.reset(
       new shared_memory_object(create_only, "shared_memory", read_write));
  } catch(...)
  {
     // executable is already running
      cerr << "Another instance of this program is running!" << endl;
      return 1;
  }

  // do something

  shared_memory_object::remove("shared_memory");  // remove the shared memory before exiting the application

Дело в том, что этот метод не позволяет моему приложению запускаться более одного раза одновременно; однако предположим, что пользователь остановил выполнение программы, тогда память не будет освобождена, и в следующий раз, когда пользователь попытается снова запустить программу, она не запустится. У Вас есть какие-то предложения ?

P.S. Консольное приложение C++, ОС: Ubuntu (но было бы идеально решение, которое будет работать и на других платформах). Спасибо


person serhatg    schedule 12.11.2014    source источник
comment
Я предполагаю, что когда вы говорите, что пользователь останавливает программу, вы имеете в виду, что пользователь нажимает CTRL-C? Это приведет к тому, что оболочка отправит вашему процессу сигнал SIGINT, который вы сможете поймать и обработать, например, для очистки ресурсов, которые в противном случае не освобождаются.   -  person Some programmer dude    schedule 12.11.2014
comment
Да, это то, что я имею в виду, а простое закрытие окна вызовет ту же проблему, я полагаю? Спасибо.   -  person serhatg    schedule 12.11.2014
comment
Вместо этого может быть лучше использовать именованный мьютекс, потому что он должен автоматически освобождаться, когда процесс умирает.   -  person cdhowie    schedule 12.11.2014
comment
Хм. В обмане это не принятый ответ. Я попрошу модератора отменить обман и вместо этого перенесу ответ сюда.   -  person sehe    schedule 12.11.2014
comment
На самом деле я сделал поиск, но не наткнулся на ваш ответ. Я проверю это сейчас, ура.   -  person serhatg    schedule 12.11.2014


Ответы (2)


Что вам нужно сделать, так это поймать неожиданное завершение программы и соответственно освободить объект общей памяти. Вы можете поймать SIGINT следующим образом, используя заголовок signal.h POSIX:

#include <signal.h>

void handleSIGINT(int param=0) {
    // Reset the shared memory object here
}

int main() {

   // Connect the signal handler to SIGINT
   signal(SIGINT, handleSIGINT);

   // Etc...

}

И вы можете таким же образом поймать завершение программы, используя функцию atexit(). Документация здесь.

person Community    schedule 12.11.2014
comment
Спасибо, это сработало хорошо, но есть ли какие-либо недостатки использования этого метода для этой цели? Я имею в виду использование объекта shared_memory_object, чтобы проверить, запущен ли другой экземпляр программы. - person serhatg; 12.11.2014
comment
@serhatg Не все сигналы могут быть захвачены (например, SIGKILL). Вы действительно должны использовать ресурс, который ОС будет автоматически освобождать, когда процесс умирает по какой-либо причине. Посмотрите на именованные мьютексы. - person cdhowie; 12.11.2014
comment
Ясно, хорошо, я посмотрю на мьютексы. Большое спасибо. - person serhatg; 12.11.2014
comment
@serhatg Я телепортировал свой существующий ответ на этот вопрос, чтобы он мог служить каноническим ответом на эту тему. Найдите его здесь - person sehe; 12.11.2014
comment
Заменено руководство cplusplus.com на руководство posix. Надеюсь, вы не против ;-) - person P.P; 12.11.2014

УВЕДОМЛЕНИЕ Ответ телепортирован из Как ограничить количество запущенных экземпляров в C++. Он уместен здесь, поскольку подробно описывает портативное решение с использованием Boost Interprocess и Boost Asio.

Обратите внимание, что решение является более общим, поскольку вы можете использовать его для ограничения количества экземпляров до определенного максимума, а не просто 1.

В Linux (и, возможно, в других ОС?) вы можете использовать идиому файла блокировки (но она не поддерживается некоторыми файловыми системами и старыми ядрами).

Я бы предложил использовать объекты межпроцессной синхронизации.

Например, используя именованный семафор Boost Interprocess:

#include <boost/interprocess/sync/named_semaphore.hpp>
#include <boost/thread.hpp>
#include <cassert>

int main()
{
    using namespace boost::interprocess;
    named_semaphore sem(open_or_create, "ffed38bd-f0fc-4f79-8838-5301c328268c", 0ul);

    if (sem.try_wait())
    {
        std::cout << "Oops, second instance\n";
    }
    else
    {
        sem.post();

        // feign hard work for 30s
        boost::this_thread::sleep_for(boost::chrono::seconds(30));

        if (sem.try_wait())
        {
            sem.remove("ffed38bd-f0fc-4f79-8838-5301c328268c");
        }
    }
}

Если вы запустите одну копию в фоновом режиме, новые копии будут «отказываться» запускаться («Ой, второй экземпляр») в течение примерно 30 секунд.

У меня есть ощущение, что здесь может быть проще изменить логику. М-м-м. Дай попробовать.

проходит некоторое время

Хе-хе. Это было сложнее, чем я думал.

Дело в том, что вы хотите убедиться, что блокировка не остается, когда ваше приложение прерывается или уничтожается. В интересах обмена методами переносимой обработки сигналов:

#include <boost/interprocess/sync/named_semaphore.hpp>
#include <boost/thread.hpp>
#include <cassert>
#include <boost/asio.hpp>

#define MAX_PROCESS_INSTANCES 3

boost::interprocess::named_semaphore sem(
        boost::interprocess::open_or_create, 
        "4de7ddfe-2bd5-428f-b74d-080970f980be",
        MAX_PROCESS_INSTANCES);

// to handle signals:
boost::asio::io_service service;
boost::asio::signal_set sig(service);

int main()
{

    if (sem.try_wait())
    {
        sig.add(SIGINT);
        sig.add(SIGTERM);
        sig.add(SIGABRT);
        sig.async_wait([](boost::system::error_code,int sig){ 
                std::cerr << "Exiting with signal " << sig << "...\n";
                sem.post();
            });
        boost::thread sig_listener([&] { service.run(); });

        boost::this_thread::sleep_for(boost::chrono::seconds(3));

        service.post([&] { sig.cancel(); });
        sig_listener.join();
    }
    else
    {
        std::cout << "More than " << MAX_PROCESS_INSTANCES << " instances not allowed\n";
    }
}

Там можно многое объяснить. Дайте мне знать, если вы заинтересованы.

ПРИМЕЧАНИЕ Должно быть совершенно очевидно, что если в вашем приложении используется kill -9 (принудительное завершение), то все ставки сняты, и вам придется либо удалить объект Name Semaphore, либо явно разблокировать его (post()).

Вот тест на моей системе:

sehe@desktop:/tmp$ (for a in {1..6}; do ./test& done; time wait)
More than 3 instances not allowed
More than 3 instances not allowed
More than 3 instances not allowed
Exiting with signal 0...
Exiting with signal 0...
Exiting with signal 0...

real    0m3.005s
user    0m0.013s
sys 0m0.012s
person sehe    schedule 12.11.2014
comment
Спасибо за ответ. Мне пока не удалось его скомпилировать из-за каких-то ошибок, связанных с asio.hpp, но я не совсем понял, как программа будет реагировать на посылку сигнала kill -9. Можем ли мы поймать этот сигнал или нет? Если да, то моя проблема с использованием shared_memory_object будет решена в принципе. Спасибо. - person serhatg; 12.11.2014
comment
Ясно, что делает этот фрагмент кода в таком случае? Как вы сказали, там много всего :) Освобождается ли семафор при завершении процесса? - person serhatg; 12.11.2014
comment
@serhatg По определению нет; сравните (for a in {1..6}; do ./test& done; time wait)& sleep 1; killall test; с (for a in {1..6}; do ./test& done; time wait)& sleep 1; killall -9 test;. Это не недостаток решения, это данность с объектами межпроцессной синхронизации (хотя на винде может быть другая ситуация, надо бы проверить). Поэтому вам нужен процесс мониторинга, который освобождает семафор. Или инструмент обслуживания, который делает это по запросу (скажем, при перезагрузке). - person sehe; 12.11.2014