【小沐学C++】C++ 禁止程序重复运行

1、使用内核对象

If the mutex is a named mutex and the object existed before this function call, the return value is a handle to the existing object, and the GetLastError function returns ERROR_ALREADY_EXISTS.

  • 互斥对象是一个同步对象,其状态在不属于任何线程时设置为信号,在拥有时设置为无信号。一次只有一个线程可以拥有互斥对象,互斥对象的名称来源于这样一个事实,即它在协调对共享资源的互斥访问方面很有用。例如,为了防止两个线程同时写入共享内存,每个线程在执行访问内存的代码之前等待互斥对象的所有权。写入共享内存后,线程将释放互斥对象。

  • 因为内核对象是可以跨进程存在的,因此我们可以通过创建一个命名互斥体(Mutex)内核对象来判断,当用同一个名字的来创建Mutex时,CreateMutex会返回一个指向该互斥体的句柄,但是GetLastError会得到ERROR_ALREADY_EXISTS的返值。因此我们就可以判断程序已有一个实例在运行。

  • 线程使用 CreateMutex 或 CreateMutexEx 函数来创建互斥对象。创建线程可以请求互斥锁对象的直接所有权,还可以指定互斥对象的名称。它还可以创建未命名的互斥锁。有关互斥锁、事件、信号量和计时器对象名称的其他信息,请参阅进程间同步。其他进程中的线程可以通过在对 OpenMutex 函数的调用中指定现有命名互斥对象的名称来打开该对象的句柄。若要将未命名互斥锁的句柄传递给另一个进程,请使用 DuplicateHandle 函数或父子句柄继承。

  • CreateMutex函数定义如下:

// Creates or opens a named or unnamed mutex object.
// To specify an access mask for the object, use the CreateMutexEx function.

HANDLE CreateMutex(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
  [in]           BOOL                  bInitialOwner,
  [in, optional] LPCSTR                lpName
);
// Creates or opens a named or unnamed mutex object and returns a handle to the object.
HANDLE CreateMutexExA(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
  [in, optional] LPCSTR                lpName,
  [in]           DWORD                 dwFlags,
  [in]           DWORD                 dwDesiredAccess
);
// Releases ownership of the specified mutex object.

BOOL ReleaseMutex(
  [in] HANDLE hMutex
);
// Opens an existing named mutex object.

HANDLE OpenMutex(
  [in] DWORD   dwDesiredAccess,
  [in] BOOL    bInheritHandle,
  [in] LPCWSTR lpName
);
  • CreateMutex示例代码一,如下:
m_hmutex = ::CreateMutex( NULL,FALSE,appID);
if(m_hmutex == NULL) return FALSE; 
if( ::GetLastError() == ERROR_ALREADY_EXISTS )
{
    return FALSE;
}
else
{
    return TRUE;
}
HANDLE RunOneByMutex(const char* appString)
{
	::SetLastError(NO_ERROR);
	HANDLE hMutex = ::CreateMutex(NULL, FALSE, appString);
	if (::GetLastError() == ERROR_ALREADY_EXISTS) {
		return NULL;	
	}

	return hMutex;
}
  • CreateMutex示例代码二,如下:
#include <windows.h>
#include <stdio.h>

#define THREADCOUNT 2

HANDLE ghMutex; 

DWORD WINAPI WriteToDatabase( LPVOID );

int main( void )
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a mutex with no initial owner

    ghMutex = CreateMutex( 
        NULL,              // default security attributes
        FALSE,             // initially not owned
        NULL);             // unnamed mutex

    if (ghMutex == NULL) 
    {
        printf("CreateMutex error: %d\n", GetLastError());
        return 1;
    }

    // Create worker threads

    for( i=0; i < THREADCOUNT; i++ )
    {
        aThread[i] = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) WriteToDatabase, 
                     NULL,       // no thread function arguments
                     0,          // default creation flags
                     &ThreadID); // receive thread identifier

        if( aThread[i] == NULL )
        {
            printf("CreateThread error: %d\n", GetLastError());
            return 1;
        }
    }

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and mutex handles

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(aThread[i]);

    CloseHandle(ghMutex);

    return 0;
}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{ 
    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwCount=0, dwWaitResult; 

    // Request ownership of mutex.

    while( dwCount < 20 )
    { 
        dwWaitResult = WaitForSingleObject( 
            ghMutex,    // handle to mutex
            INFINITE);  // no time-out interval
 
        switch (dwWaitResult) 
        {
            // The thread got ownership of the mutex
            case WAIT_OBJECT_0: 
                __try { 
                    // TODO: Write to the database
                    printf("Thread %d writing to database...\n", 
                            GetCurrentThreadId());
                    dwCount++;
                } 

                __finally { 
                    // Release ownership of the mutex object
                    if (! ReleaseMutex(ghMutex)) 
                    { 
                        // Handle error.
                    } 
                } 
                break; 

            // The thread got ownership of an abandoned mutex
            // The database is in an indeterminate state
            case WAIT_ABANDONED: 
                return FALSE; 
        }
    }
    return TRUE; 
}
  • CreateMutex示例代码三,如下:
#include <windows.h>
 
class CMutexLock
{
public:
	CMutexLock() :_mutex(NULL)
	{
		this->_mutex = CreateMutex(NULL, FALSE, TEXT("_my_mutex_lock"));
	}
 
	virtual ~CMutexLock()
	{
		if (this->_mutex)
		{
			CloseHandle(this->_mutex);
			this->_mutex = NULL;
		}
	}
 
	CMutexLock(const CMutexLock& mutex) { *this = mutex; }
 
	CMutexLock& operator=(const CMutexLock& other)
	{
		if (this != &other)
		{
			this->_mutex = other._mutex;
		}
		return *this;
	}

	void Lock()
	{
		WaitForSingleObject(this->_mutex, INFINITE);
	}
 
	void UnLock()
	{
		ReleaseMutex(this->_mutex);
	}
 
 private:
	HANDLE _mutex;
	
};

2、使用共享数据段

背景知识:EXE和DLL文件映像由许多区组成如代码在.text段中,初始化数据在.data段中,未初始化数据在.bss段中。系统在加载EXE和DLL时,实际上是使用了内存映射,为了减少加载时间,同一EXE文件多个实例实际在系统中只有一份。但一般地如果其中某个实例对某个数据区进行写时,系统会使用Copy-On-Write机制将这个数据区在虚拟内存中复制一份出来,并映射到该实例原先的地址空间,也就实现了进程数据的唯一性,而不会干扰其它进程。但是我们可以通过设置让系统关闭掉这个机制。哪么如何做呢?

在Visual Studio中你可以程序中加上以下几行:
#pragmacomment(linker,“/SECTION:Share,RWS”) //指示编译器Share是可读写和共享的,当然你也可以通过设置链接器选项直接加上 /SECTION:Share,RWS,不过我更喜欢这个,因此其他朋友就不必自已去设置这个选项了。

// 开始自已的数据段
#pragmadata_seg("Share")  
//必需初始化,否则不会将编译器不会将其放入Share这个区
int g_AppInit =0;  
#pragmadata_seg()

还有一种将某个变量置于特别数据段的方式

__declspec(allocate("Share")) int g_AppInit =0;

这种方式的好处是无论你初不初始化这个变量都将置于该Share段内
哪么如何判断呢是否启动了实例呢,很简单,看以下代码

g_AppInit ++ ;
if(g_AppInit >1)
{
	AfxMessageBox("A instance are runed!");
	return FALSE;
}

相关的问题:如何通知前一个实例
解决了重复启动的问题,为了获得更佳的用户体验,往往我们要使前一个实例激活,如何做呢?使用消息是一个不错的方法。首先你需要在启动程序时登记一个全局消息。

WM_APPACTIVE = ::RegisterWindowMessage(appID);  

相同的appID字符串会给出相同的消息值,并且总是在0xC000- 0xFFFF区间中,然后当你发现已启动程序实例时通知前一个实例:

DWORD dectype = BSM_APPLICATIONS;   //仅向应用程序发送
BroadcastSystemMessage(BSF_POSTMESSAGE,&dectype, WM_APPACTIVE, 0,0);

前一个程序实例收到这个消息后,进行处理,可以前置窗口激活等等。

3、使用信号量(Semaphore)

信号量 (semaphore) 是一种轻量的同步原件,用于制约对共享资源的并发访问( 控制线程的并发数量) 。在可以使用两者时,信号量能比条件变量更有效率。信号量是操作系统提供的一种协调共享资源访问的方法。信号量则由操作系统进行管理,地位高于进程,操作系统保证信号量的原子性。

信号量的概念是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程目前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)

信号量(英语:semaphore)又称为信号标或者信号灯,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。

  • CreateSemaphore函数形式:
// Creates or opens a named or unnamed semaphore object.
HANDLE CreateSemaphore(
  [in, optional] LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  [in]           LONG                  lInitialCount,
  [in]           LONG                  lMaximumCount,
  [in, optional] LPCWSTR               lpName
);
// Increases the count of the specified semaphore object by a specified amount.
BOOL ReleaseSemaphore(
  [in]            HANDLE hSemaphore,
  [in]            LONG   lReleaseCount,
  [out, optional] LPLONG lpPreviousCount
);
  • CreateSemaphore示例代码一:
bool RunOneBySemaphore(const char* appString)
{
	::SetLastError(NO_ERROR);
	HANDLE hSem = CreateSemaphore(NULL, 1, 1, appString);
	if(hSem)
	{
		if(ERROR_ALREADY_EXISTS == GetLastError())
		{
			CloseHandle(hSem);
			hSem = NULL;

			//AfxMessageBox(_T("已有一个实例在运行!"));
			return false;
		}
	}
	else
	{
		//AfxMessageBox(_T("创建唯一对象失败,程序退出!"));
		return false;
	}
	return hSem != NULL;
}
  • CreateSemaphore示例代码二:
#include <windows.h>
#include <stdio.h>

#define MAX_SEM_COUNT 10
#define THREADCOUNT 12

HANDLE ghSemaphore;

DWORD WINAPI ThreadProc( LPVOID );

int main( void )
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a semaphore with initial and max counts of MAX_SEM_COUNT

    ghSemaphore = CreateSemaphore( 
        NULL,           // default security attributes
        MAX_SEM_COUNT,  // initial count
        MAX_SEM_COUNT,  // maximum count
        NULL);          // unnamed semaphore

    if (ghSemaphore == NULL) 
    {
        printf("CreateSemaphore error: %d\n", GetLastError());
        return 1;
    }

    // Create worker threads

    for( i=0; i < THREADCOUNT; i++ )
    {
        aThread[i] = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) ThreadProc, 
                     NULL,       // no thread function arguments
                     0,          // default creation flags
                     &ThreadID); // receive thread identifier

        if( aThread[i] == NULL )
        {
            printf("CreateThread error: %d\n", GetLastError());
            return 1;
        }
    }

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and semaphore handles

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(aThread[i]);

    CloseHandle(ghSemaphore);

    return 0;
}

DWORD WINAPI ThreadProc( LPVOID lpParam )
{

    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwWaitResult; 
    BOOL bContinue=TRUE;

    while(bContinue)
    {
        // Try to enter the semaphore gate.

        dwWaitResult = WaitForSingleObject( 
            ghSemaphore,   // handle to semaphore
            0L);           // zero-second time-out interval

        switch (dwWaitResult) 
        { 
            // The semaphore object was signaled.
            case WAIT_OBJECT_0: 
                // TODO: Perform task
                printf("Thread %d: wait succeeded\n", GetCurrentThreadId());
                bContinue=FALSE;            

                // Simulate thread spending time on task
                Sleep(5);

                // Release the semaphore when task is finished

                if (!ReleaseSemaphore( 
                        ghSemaphore,  // handle to semaphore
                        1,            // increase count by one
                        NULL) )       // not interested in previous count
                {
                    printf("ReleaseSemaphore error: %d\n", GetLastError());
                }
                break; 

            // The semaphore was nonsignaled, so a time-out occurred.
            case WAIT_TIMEOUT: 
                printf("Thread %d: wait timed out\n", GetCurrentThreadId());
                break; 
        }
    }
    return TRUE;
}

4、使用事件Event

事件对象是一个同步对象,其状态可以通过使用 SetEvent 函数显式设置为信号。以下是两种类型的事件对象。

事件对象在向线程发送指示特定事件已发生的信号时很有用。例如,在重叠输入和输出中,当重叠操作完成时,系统将指定的事件对象设置为信号状态。单个线程可以在多个同时重叠的操作中指定不同的事件对象,然后使用其中一个多对象等待函数来等待任何一个事件对象的状态发出信号。

多个进程可以具有同一事件、互斥锁、信号量或计时器对象的句柄,因此这些对象可用于完成进程间同步。创建对象的过程可以使用创建函数(CreateEvent、CreateMutex、CreateSemaphore 或 CreateWaitableTimer)返回的句柄。 其他进程可以使用对象名称或通过继承或复制打开对象的句柄。

多个进程可以具有同一事件对象的句柄,从而允许将对象用于进程间 同步。可以使用以下对象共享机制:

  • 由创建进程函数创建的子进程 可以继承事件对象的句柄,如果 CreateEvent 的 lpEventAttributes 参数启用了继承。
  • 进程可以在对 DuplicateHandle 函数的调用中指定事件对象句柄以创建副本 可由其他进程使用的句柄。
  • 进程可以在对 OpenEvent 或 CreateEvent 函数的调用中指定事件对象的名称。

使用 CloseHandle 函数关闭控点。这 当进程终止时,系统会自动关闭句柄。事件对象在其最后一个时被销毁 句柄已关闭。

  • CreateEvent函数形式:
// Creates or opens a named or unnamed event object.
// To specify an access mask for the object, use the CreateEventEx function.

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
  BOOL bManualReset,                       // reset type
  BOOL bInitialState,                      // initial state
  LPCTSTR lpName                           // object name
);
HANDLE OpenEvent(
  [in] DWORD  dwDesiredAccess,
  [in] BOOL   bInheritHandle,
  [in] LPCSTR lpName
);
BOOL ResetEvent(
  [in] HANDLE hEvent
);
  • CreateEvent代码示例一:
#ifdef _AFX
// Makes sure only one instance of the application is running.
BOOL IsSingleInstance( const char* AppTitle /*= NULL */ )
{
	HANDLE hEvent;
	hEvent = ::CreateEvent(NULL,FALSE,FALSE, (AppTitle==NULL ? AfxGetAppName() : AppTitle ));
	if( hEvent==NULL ) {
		// Something REALLY went wrong
		return FALSE;
	};
	if( ::GetLastError()==ERROR_ALREADY_EXISTS ) {
		// Some other instance is running!
		::CloseHandle( hEvent );
		return FALSE;
	};
	// System closes handle automatically when process terminates
	return TRUE;
}
  • CreateEvent代码示例二:
#include <windows.h>
#include <stdio.h>

#define THREADCOUNT 4 

HANDLE ghWriteEvent; 
HANDLE ghThreads[THREADCOUNT];

DWORD WINAPI ThreadProc(LPVOID);

void CreateEventsAndThreads(void) 
{
    int i; 
    DWORD dwThreadID; 

    // Create a manual-reset event object. The write thread sets this
    // object to the signaled state when it finishes writing to a 
    // shared buffer. 

    ghWriteEvent = CreateEvent( 
        NULL,               // default security attributes
        TRUE,               // manual-reset event
        FALSE,              // initial state is nonsignaled
        TEXT("WriteEvent")  // object name
        ); 

    if (ghWriteEvent == NULL) 
    { 
        printf("CreateEvent failed (%d)\n", GetLastError());
        return;
    }

    // Create multiple threads to read from the buffer.

    for(i = 0; i < THREADCOUNT; i++) 
    {
        // TODO: More complex scenarios may require use of a parameter
        //   to the thread procedure, such as an event per thread to  
        //   be used for synchronization.
        ghThreads[i] = CreateThread(
            NULL,              // default security
            0,                 // default stack size
            ThreadProc,        // name of the thread function
            NULL,              // no thread parameters
            0,                 // default startup flags
            &dwThreadID); 

        if (ghThreads[i] == NULL) 
        {
            printf("CreateThread failed (%d)\n", GetLastError());
            return;
        }
    }
}

void WriteToBuffer(VOID) 
{
    // TODO: Write to the shared buffer.
    
    printf("Main thread writing to the shared buffer...\n");

    // Set ghWriteEvent to signaled

    if (! SetEvent(ghWriteEvent) ) 
    {
        printf("SetEvent failed (%d)\n", GetLastError());
        return;
    }
}

void CloseEvents()
{
    // Close all event handles (currently, only one global handle).
    
    CloseHandle(ghWriteEvent);
}

int main( void )
{
    DWORD dwWaitResult;

    // TODO: Create the shared buffer

    // Create events and THREADCOUNT threads to read from the buffer

    CreateEventsAndThreads();

    // At this point, the reader threads have started and are most
    // likely waiting for the global event to be signaled. However, 
    // it is safe to write to the buffer because the event is a 
    // manual-reset event.
    
    WriteToBuffer();

    printf("Main thread waiting for threads to exit...\n");

    // The handle for each thread is signaled when the thread is
    // terminated.
    dwWaitResult = WaitForMultipleObjects(
        THREADCOUNT,   // number of handles in array
        ghThreads,     // array of thread handles
        TRUE,          // wait until all are signaled
        INFINITE);

    switch (dwWaitResult) 
    {
        // All thread objects were signaled
        case WAIT_OBJECT_0: 
            printf("All threads ended, cleaning up for application exit...\n");
            break;

        // An error occurred
        default: 
            printf("WaitForMultipleObjects failed (%d)\n", GetLastError());
            return 1;
    } 
            
    // Close the events to clean up

    CloseEvents();

    return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParam) 
{
    // lpParam not used in this example.
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwWaitResult;

    printf("Thread %d waiting for write event...\n", GetCurrentThreadId());
    
    dwWaitResult = WaitForSingleObject( 
        ghWriteEvent, // event handle
        INFINITE);    // indefinite wait

    switch (dwWaitResult) 
    {
        // Event object was signaled
        case WAIT_OBJECT_0: 
            //
            // TODO: Read from the shared buffer
            //
            printf("Thread %d reading from buffer\n", 
                   GetCurrentThreadId());
            break; 

        // An error occurred
        default: 
            printf("Wait error (%d)\n", GetLastError()); 
            return 0; 
    }

    // Now that we are done reading the buffer, we could use another
    // event to signal that this thread is no longer reading. This
    // example simply uses the thread handle for synchronization (the
    // handle is signaled when the thread terminates.)

    printf("Thread %d exiting\n", GetCurrentThreadId());
    return 1;
}

后记

如果你觉得这些文字有一点点用处,可以给作者点个赞;╮( ̄▽ ̄)╭
如果你感觉作者写的不咋地//(ㄒoㄒ)//,就在评论处留言,作者继续改进。o_O???
谢谢各位小伙伴们啦( ´ ▽ ‘)ノ ( ´ ▽ ` )っ!!!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值