1 实现原理
程序在启动第一个实例时,会先尝试attach一块指定key的共享内存,由于这个共享内存事先并不存在,所以尝试attach失败。失败之后,第一个实例会create这块指定key的共享内存。
当程序启动第二个实例时,依然会先尝试attach一块指定key的共享内存,由于这个共享内存刚被第一个实例创建,所以尝试attach返回true。于是得知,当前实例并非第一个实例,便不允许第二个实例继续启动。
流程图如下:
Ps:
如果不考虑任何的共享内存残留或者sharedMem已经创建的情况,直接进行attach()会返回失败, 因为sharedMem还没有被create呢。
sharedMem还未被create的情况下,直接进行attach()会返回false。
但考虑到某些特殊情况(已有实例启动)下,由于操作系统中存在已被创建的sharedMem,这时attach就会返回true。
经过测试,QT程序异常奔溃(访问野指针)后,再重新启动实例sharedMem.attach()将返回false。 也就说,QT程序异常奔溃(访问野指针)后,系统将sharedMem作为资源回收了。
2 代码实现
2.1 头文件
runguard.h
#ifndef RUNGUARD_H
#define RUNGUARD_H
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
class RunGuard
{
public:
explicit RunGuard(const QString& key);
~RunGuard();
bool tryToRun();
void release();
private:
const QString memLockKey;
const QString sharedmemKey;
QSharedMemory sharedMem;
QSystemSemaphore memLock;
Q_DISABLE_COPY( RunGuard )
};
#endif // RUNGUARD_H
说明:
Q_DISABLE_COPY宏作用是禁止对给定的类使用复制构造函数和赋值运算符。
Q_DISABLE_COPY作用:
「Q_DISABLE_COPY宏作用是禁止对给定的类使用复制构造函数和赋值运算符。」 Q_DISABLE_COPY宏多使用在QObject类或其派生类中。如果我们需要禁止用户复制或赋值类对象,在类内使用Q_DISABLE_COPY宏即可。
如果某些类内有指针成员变量,那么在复制或赋值很容易时候就会出现浅拷贝问题,当「复制类」的指针成员变量被释放时,「原类」内的指针成员变量再次释放就会导致程序意向不到的后果(二次析构)。
2.2 实现文件
runguard.cpp
#include "runguard.h"
#include <QCryptographicHash>
//椒盐哈希
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) :
memLockKey( generateKeyHash( key, "_memLockKey" ) ),
sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) ),
sharedMem( sharedmemKey ),
memLock( memLockKey, 1 )
{
}
RunGuard::~RunGuard()
{
release();
}
//返回false说明已经有实例在运行了,不能启动额外的实例。
bool RunGuard::tryToRun()
{
/*@ sharedMem.attach():
* Returns true if the attach operation is successful.
* If false is returned, call error() to determine which error occurred.
* After attaching the shared memory segment, a pointer to the shared memory
* can be obtained by calling data().
*/
//如果不考虑任何的共享内存残留或者sharedMem已经创建的情况,直接进行attach()会返回失败,
//因为sharedMem还没有被create呢。
//sharedMem还未被create的情况下,直接进行attach()会返回false。
//但考虑到某些特殊情况已有实例启动)下,由于操作系统中存在已被创建的sharedMem,这时attach就会返回true.
//经过测试,QT程序异常奔溃(访问野指针)后,再重新启动实例sharedMem.attach()将返回false。
//也就说,QT程序异常奔溃(访问野指针)后,系统将sharedMem作为资源回收了。
memLock.acquire();
const bool isRunning = sharedMem.attach();
//这时后要考虑将当前进程实例与sharedMem之间解耦(detach)。
if ( isRunning )
sharedMem.detach();
memLock.release();
if(isRunning == true)//已有实例启动
return false;
memLock.acquire();
const bool result = sharedMem.create( sizeof( quint64 ) );//创建共享内存
memLock.release();
if ( !result )
{
release();
return false;
}
return true;
}
void RunGuard::release()
{
memLock.acquire();
if ( sharedMem.isAttached() )
sharedMem.detach();
memLock.release();
}