Согласно документу Qt, приобретенный QSystemSemaphore
не будет автоматически выпущен, если процесс выйдет из строя, не вызвав его деструктор в Unix-подобных операционных системах. Это может стать причиной тупика в другом процессе, пытающегося получить тот же семафор. Если вы хотите быть на 100% уверенным, что ваша программа правильно справляется с аварийными ситуациями, и если вы не настаиваете на использовании Qt, вы можете использовать другие механизмы блокировки, которые операционные системы автоматически освобождают, когда процесс умирает - например, lockf()
и флаг O_EXLOCK
, переданный в open()
, которые упомянуты в . Как восстановить семафор, когда процесс, который уменьшил его до нуля, сработает? или flock()
. На самом деле создание общей памяти больше не требуется, если используется flock()
. Просто использовать flock()
достаточно для защиты одного экземпляра приложения.
Если восстановление семафора от сбоев в Unix не имеет значения, я думаю, что RunGuard из ответа Дмитрия Сазонова все еще может несколько упрощается:
~RunGuard()
и RunGuard::release()
может быть снят, поскольку QSharedMemory
будет автоматически отсоединяться от сегмента разделяемой памяти после его уничтожения, как в документе Qt для QSharedMemory::~QSharedMemory()
: «Деструктор очищает ключ, который заставляет объект разделяемой памяти отсоединяться от основного сегмента разделяемой памяти». RunGuard::isAnotherRunning()
также может быть снята. Целью является эксклюзивное исполнение. Как отметил @Nejat, мы можем просто воспользоваться тем фактом, что в любой момент может быть создано не более одного сегмента разделяемой памяти для данного ключа, как в документе Qt для QSharedMemory::create()
: «Если сегмент разделяемой памяти, идентифицированный ключ уже существует, операция attach не выполняется и возвращается false. "QSharedMemory
в конструкторе - уничтожить сегмент разделяемой памяти, который выживает из-за предыдущего сбоя процесса, как в документе Qt: «Unix: ... Когда последний поток или процесс, имеющий экземпляр QSharedMemory
, прикрепленный к определенному сегменту разделяемой памяти, отделяется от сегмента, уничтожая его экземпляр QSharedMemory
, ядро Unix выпускает сегмент разделяемой памяти. Но если этот последний поток или процесс выйдет из строя, не запуская деструктор QSharedMemory
, сегмент разделяемой памяти сохранится после сбоя. ". Когда «fix» будет уничтожено, неявный detach()
должен быть вызван его деструктором, и сегмент оставшихся разделяемых разделов, если он есть, будет выпущен. QSharedMemory
является потокобезопасным / безопасный процесс или нет. В противном случае код, относящийся к memLock
, может быть удален, если управление потоком осуществляется внутренне с помощью QSharedMemory
. С другой стороны, fix
также должен быть защищен memLock
, если безопасность является проблемой: RunGuard::RunGuard( const QString& key )
: key( key )
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
, sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) )
, sharedMem( sharedMemKey )
, memLock( memLockKey, 1 )
{
memLock.acquire();
{
QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
fix.attach();
}
memLock.release();
}
, поскольку явные attach()
и неявные detach()
вызываются вокруг fix
. RunGuard
выглядит следующим образом: Использование: int main()
{
RunGuard guard( "some_random_key" );
if ( !guard.tryToRun() )
return 0;
QAppplication a(/*...*/);
// ...
}
runGuard.h: #ifndef RUNGUARD_H
#define RUNGUARD_H
#include
#include
#include
class RunGuard
{
public:
RunGuard( const QString& key );
bool tryToRun();
private:
const QString key;
const QString memLockKey;
const QString sharedMemKey;
QSharedMemory sharedMem;
QSystemSemaphore memLock;
Q_DISABLE_COPY( RunGuard )
};
#endif // RUNGUARD_H
runGuard.cpp: #include "runGuard.h"
#include
namespace
{
QString generateKeyHash( const QString& key, const QString& salt )
{
QByteArray data;
data.append( key.toUtf8() );
data.append( salt.toUtf8() );
data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
return data;
}
}
RunGuard::RunGuard( const QString& key )
: key( key )
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
, sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) )
, sharedMem( sharedMemKey )
, memLock( memLockKey, 1 )
{
QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
fix.attach();
}
bool RunGuard::tryToRun()
{
memLock.acquire();
const bool result = sharedMem.create( sizeof( quint64 ) );
memLock.release();
if ( !result )
return false;
return true;
}
bool RunGuard::tryToRun()
{
if ( isAnotherRunning() ) // Extra check
return false;
// (tag1)
memLock.acquire();
const bool result = sharedMem.create( sizeof( quint64 ) ); // (tag2)
memLock.release();
if ( !result )
{
release(); // (tag3)
return false;
}
return true;
}
Рассмотрим сценарий: Когда текущий процесс ProcCur работает до (tag1)
, происходит следующее: (обратите внимание, что (tag1)
находится вне защиты блокировки) Другой процесс ProcOther с использованием RunGuard
начинает работать. ProcOther работает до (tag2)
и успешно создает общую память. ProcOther сбой, прежде чем он называет release()
в (tag3)
. ProcCur продолжает работать с (tag1)
. ProcCur работает до (tag2)
и пытается создать общую память. Однако sharedMem.create()
вернет false
, потому что ProcOther оставили созданную. Как мы видим в документе QSharedMemory::create()
: «Если сегмент разделяемой памяти, идентифицированный ключом, уже существует, операция attach не выполняется и возвращается false». Наконец, RunGuard::tryToRun()
в ProcCur вернет false
, что не так ожидаемо, потому что ProcCur является единственным существующим процессом, использующим RunGuard
.