跳到主要内容

通过关闭互斥体实现多客户端运行

通过关闭互斥体实现多客户端运行

为了防止客户端被多次启动,开发者们通常会使用创建互斥体(Mutex)对象的方式进行实现。互斥锁是一种用于多线程编程中,保护共享资源不被多个线程或进程同时访问的机制。

Windows 是如何打开互斥对象?

这里使用 Windows 的 API CreateMutexA function (synchapi.h)

HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
[in] BOOL bInitialOwner,
[in, optional] LPCSTR lpName
);

这个命令是 Windows API 中的 CreateMutexA 函数,用于创建或打开一个互斥体(Mutex)。互斥体是同步对象的一种,用来控制多个线程对共享资源的访问。在多线程编程中,为了防止数据冲突和确保数据一致性,通常需要使用互斥体或其他同步机制来实现线程同步。

这个函数具体参数解释如下:

  • LPSECURITY_ATTRIBUTES lpMutexAttributes[可选] 指向一个 SECURITY_ATTRIBUTES 结构体的指针,这个结构体决定了返回的句柄是否可以被子进程继承。如果不需要设置安全属性,可以传递 NULL
  • BOOL bInitialOwner:如果这个值为 TRUE,表示创建互斥体的线程立即成为该互斥体的拥有者。如果为 FALSE,则表示线程不需要立即拥有该互斥体。
  • LPCSTR lpName[可选] 指向互斥体名称的指针。如果该参数不为 NULL,函数将创建一个具有该名称的互斥体,这允许多个进程通过指定相同的互斥体名称来访问同一个互斥体对象。如果参数为 NULL,创建的互斥体将是匿名的。

函数返回值是 HANDLE,如果函数执行成功,返回值是新创建或打开的互斥体对象的句柄。如果函数失败,返回值为 NULL。可以通过调用 GetLastError 函数来获取错误代码,了解函数调用失败的具体原因。

注意,CreateMutexA 是 ANSI 版本的函数,处理的是 ANSI 字符集。对于使用 Unicode 字符集的应用,应使用 CreateMutexW 函数。

它的返回值:

  • 如果函数成功,则返回值是新创建的互斥对象的句柄(Handle)。
  • 如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用 [GetLastError] 函数。
  • 如果互斥锁是一个命名互斥锁并且该对象在此函数调用之前存在,则返回值是现有对象的句柄,并且 [GetLastError] 函数返回 ERROR_ALREADY_EXISTS

使用例子:

假设我们有一个简单的 Windows 控制台应用程序,我们想要确保在任何时刻只有一个线程可以进入某个关键代码段。我们可以使用 CreateMutexA 来创建一个互斥体,并在访问共享资源之前请求互斥体的所有权,在访问完成后释放互斥体。

#include <windows.h>
#include <stdio.h>

// 全局互斥体句柄
HANDLE g_hMutex;

// 线程函数原型
DWORD WINAPI ThreadFunction(LPVOID lpParam);

int main()
{
// 创建互斥体,初始拥有者为FALSE,匿名互斥体(不命名)
g_hMutex = CreateMutexA(NULL, FALSE, NULL);
if (g_hMutex == NULL)
{
printf("CreateMutex error: %d\n", GetLastError());
return 1;
}

// 创建两个线程
HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);

// 等待线程结束
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);

// 关闭句柄
CloseHandle(g_hMutex);
CloseHandle(hThread1);
CloseHandle(hThread2);

return 0;
}

DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
// 尝试获取互斥体的所有权
WaitForSingleObject(g_hMutex, INFINITE);

// 进入关键代码段
printf("Thread %d entering critical section.\n", GetCurrentThreadId());
// 模拟一些工作,比如睡眠一段时间
Sleep(2000);
printf("Thread %d leaving critical section.\n", GetCurrentThreadId());

// 释放互斥体
ReleaseMutex(g_hMutex);

return 0;
}

通过句柄关闭互斥体

现在我们知道了如何创建互斥体,那么如何关闭一个已经打开的互斥体呢?

首先使用 Process Explorer 工具找到互斥体的句柄。

提示

Process Explorer 是 Microsoft 官方提供的用于找出进程已打开或加载的 HandleDLL 信息的工具。它的官方页面:https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer

示例(使用管理员打开):

  1. 以 WeChat 为例,首先启动 WeChat,在 [Process Explorer] 主界面中找到名为 WeChat.exe 进程并选中。

  2. 然后在 Lower Pane 界面中找到 TypeMutantName\Sessions\1\BaseNamedObjects\_WeChat_App_Instance_Identity_Mutex_NameHandle

  3. 右键单击 Close Handle 关闭句柄后,即可启动一个新进程。

这里使用 Python 代码自动实现关闭互斥体的功能。

handles = handler.find_handles(process_ids=[10000], 
handle_names=[r'\Sessions\1\BaseNamedObjects\_WeChat_App_Instance_Identity_Mutex_Name'])
handler.close_handles(handles)