Сейчас я активно пишу под Qt. И встала у меня задачка проверить при запуске приложения, а не запущено ли оно уже. Под линуксом. Велосипеды городить не хотелось, хотелось родить что-то используя готовые классы Qt, чтобы сразу было кроссплатформенно. В винде, например, есть готовое решение — именованные мьютексы (или семафоры, неважно, главное именованные). Стал гуглить. Ага, в Qt есть нечто подобное, называется QSystemSemaphore
. Работает так:
— создаем экземпляр класса QSystemSemaphore
, он создает глобальный (системный) семафор
— вызываем acquire
, уменьшаем счетчик семафора на единицу
— что-то делаем
— вызовом release
, увеличиваем счетчик семафора на единицу
Вобщем класический семафор, только глобальный. Понятно, что если создать семафор с изначальным значением счетчика равным 1 и вызвать acquire
, то пока мы не вызовем release
никто больше не сможет этот семафор захватить. Ура, решение найдено! Вырисовывается сразу следующий код одноэкземплярного приложения:
— создаем семафор
— захватываем
— приложение работает
— после завершение работы приложение освобождает семафор
Все здорово, все прекрасно. Документация обещает нам, что даже если не вызвать release
(например программа скрашилась) система все равно вернет в семафор все захваченные у него единицы. Но возникает проблема. Если пользователь запустит второй экземплар приложения — как он об этом узнает? Вызов метода acquire
блокирующий и пока семафор не освободиться приложение будет «висеть», а пользователь в недоумении чего-то ждать.
Казалось бы — не проблема. Например тот же QMutex
помимо вызова lock
для захвата мьютекса имеет вызов и try_lock
для неблокирующей попытки захвата. Должно что-то подобное быть и у системного семафора. Но — увы — его нет. Я долго и безуспешно гуглил — на форумах полно подобных тем и ни одного работающего решения. Проблема? Проблема. Интересная? Весьма. Я взялся ее решать.
При помощи одного QSystemSemaphore
очевидно ничего не разрешится. Что в Qt еще есть из глобального? Еще есть QSharedMemory
. Разделяемая между разными приложениями память. Работает так:
— создаем экземпляр класса QSharedMemory
— вызываем attach
для подсоединения к уже созданной памяти
— если подсоединитсья не удалось — вызываем create
чтобы создать разделяемую память
— работаем с разделяемой памятью (например передаем какие-то данные в другой процесс)
— деструктор сам вызывает detach
и отсоединение от разделяемой памяти
Казалось бы — вот оно! Если вызов attach
прошел успешно, значит приложение уже запущено. Если нет — приложение запущено впервые, вызываем create
и, таким образом, закрепляем свое право первенства в вопросе владения разделяемой памятью (т.е. у всех остальных attach
будет происходить успешно уже). Проблемы здесь, собственно, две.
Первая — гонки. Два процесса могут одномервменно вызвать attach
, потрепеть неудачу, оба будут считать что они — первые и вызовут create
. Один создаст память, другой — потерпит неудачу. Нехорошо, но, вобщем, терпимо. Можно обойти.
Вторая проблема посерьезнее. Оказывается линуксовая реализация QSharedMemory
устроена так, что если для памяти не будет вызван detach
она не разрушается. Проще говоря, если процесс присоединился к разделяемой памяти, а затем аварийно завершился, то память останется созданной. И следующий вызов attach
пройдет успешно. Т.е. аварийное завершение единственного экземпляра приложения приведет к тому, что больше ни одного экземпляра приложения создано не будет. Мда.
Таким образом имеем два класса — QSystemSemaphore
и QSharedMemory
, каждый из которых отчасти реализует нужную функциональность, но полностью конечную задачу не решает. Может быть удасться два этих «недокласса» гармонично сочетать?
И таки да! QSystemSemaphore
решает первую проблему QSharedMemory
, проблему гонок. Код:
QSustemSemaphore sema("<Unique name1>", 1);
bool isRunning;
sema.acquire();
QSharedMemory shmem("<Unique name2>");
if (shmem.attach())
{
isRunning = true;
}
else
{
shmem.create(1);
isRunning = false;
}
sema.release();
Проще говоря глобальный семафор синхронизирует управление разделяемой памятью. Как говорится, элементарно, Ватсон.
Вторая проблема, как оказалось, тоже имеет решение. Довольно смешное. Чтобы линуксовая реализация разделяемой памяти проверила счетчик ссылок на нее и разрушила ее, если ссылок больше нет, достаточно присоединиться к ней и отсоединиться. И если в результате аварийного завершения предыдущего экземпляра приложения осталась неразруженая разделяемая память с нулевым (фактически) счетчиком ссылок подсоединение к ней и немедленное отсоединение разрушит эту память. А если счетчик ссылок ненулевой — он останется прежним. Что, вобщем, и требуется.
Окончательно код выглядит так:
QSustemSemaphore sema("<Unique name1>", 1);
bool isRunning;
sema.acquire();
{
QSharedMemory shmem("<Unique name2>");
shmem.attach();
}
QSharedMemory shmem("<Unique name2>");
if (shmem.attach())
{
isRunning = true;
}
else
{
shmem.create(1);
isRunning = false;
}
sema.release();
Ясно, что второй экземпляр класса QSharedMemory
нужно сохранить до конца работы приложения. И тогда все работает именно так, как нужно.
Автор: gcooler