Деструктор ~RunGuard()
и RunGuard::release()
могут быть сняты, поскольку QSharedMemory
автоматически отсоединится от сегмента общей памяти после его уничтожения, как в документе Qt для QSharedMemory::~QSharedMemory()
: «Деструктор очищает ключ, что вынуждает объект общей памяти отсоединяться от его основного сегмента общей памяти.».
RunGuard::isAnotherRunning()
также может быть удалено.Цель - эксклюзивное исполнение.Как упомянул @Nejat, мы можем просто воспользоваться тем фактом, что в любой момент для данного ключа может быть создано не более одного сегмента совместно используемой памяти, как в документе Qt для QSharedMemory::create()
: «Если сегмент совместно используемой памяти идентифицированключ уже существует, операция присоединения не выполняется и возвращается false. "
Если я правильно понимаю, цель" исправить "объект QSharedMemory
в конструкторе - уничтожитьсегмент общей памяти, который выживает из-за предыдущего сбоя процесса, как в документе Qt: "Unix: ... Когда последний поток или процесс, экземпляр которого QSharedMemory
подключен к определенному сегменту общей памяти, отсоединяется от сегмента с помощьюуничтожив свой экземпляр QSharedMemory
, ядро Unix освобождает сегмент общей памяти. Но если последний поток или процесс завершается сбоем без запуска деструктора QSharedMemory
, сегмент общей памяти переживает сбой. "Когда «исправление» уничтожается, деструктор должен вызвать неявный 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 <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
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 <QCryptographicHash>
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()
at (tag3)
. - ProcCur продолжает работать с
(tag1)
. - ProcCur работает с
(tag2)
и пытается создатьОбщая память.Однако sharedMem.create()
вернет false
, потому что ProcOther оставил созданный.Как мы видим из документа QSharedMemory::create()
: «Если сегмент общей памяти, идентифицируемый ключом, уже существует, операция присоединения не выполняется и возвращается false.» - Наконец,
RunGuard::tryToRun()
в ProcCur вернет false
, что не так, как ожидалось, потому что ProcCur - единственный существующий процесс, использующий RunGuard
.