概述:系统锁利用命名互斥体(Named Mutex)实现进程互斥,常用于确保程序只能运行一个实例。
[toc]
系统锁原理
系统锁的核心是使用 CreateMutex 创建一个命名互斥体。命名的内核对象在 Windows 系统中是全局可见的,因此:
- 首次创建时,成功返回互斥体句柄
- 再次使用相同名称创建时,系统会返回已存在的句柄,并通过
GetLastError()返回ERROR_ALREADY_EXISTS - 利用这一特性,可以实现进程单实例运行
核心函数
HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性,NULL 表示默认
BOOL bInitialOwner, // 初始拥有者,FALSE 表示不立即拥有
LPCSTR lpName // 互斥体名称,NULL 表示匿名
);返回值:
- 成功:返回互斥体句柄
- 如果互斥体已存在:返回已存在的句柄,
GetLastError()返回ERROR_ALREADY_EXISTS (183)
使用场景
| 场景 | 说明 |
|---|---|
| 单实例应用 | 确保程序只能运行一个实例 |
| 跨进程同步 | 多个进程间的互斥访问 |
| 资源锁定 | 防止多个进程同时访问共享资源 |
| 防重复执行 | 防止关键操作被重复执行 |
完整代码示例
基础版:单实例检测
#include <windows.h>
#include <iostream>
bool IsAnotherInstanceRunning(const char* mutexName) {
HANDLE hMutex = CreateMutexA(NULL, FALSE, mutexName);
if (hMutex == NULL) {
std::cerr << "CreateMutex failed: " << GetLastError() << std::endl;
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// 互斥体已存在,说明已有实例运行
CloseHandle(hMutex);
return true;
}
// 当前是第一个实例,hMutex 需要在程序退出时关闭
// 这里简化处理,实际应该保存句柄
return false;
}
int main() {
const char* appName = "Global\\MyUniqueAppName_SingleInstance";
if (IsAnotherInstanceRunning(appName)) {
std::cout << "程序已在运行,退出..." << std::endl;
return 1;
}
std::cout << "程序启动成功" << std::endl;
std::cout << "按回车键退出..." << std::endl;
std::cin.get();
return 0;
}进阶版:RAII 封装
#include <windows.h>
#include <string>
#include <stdexcept>
class WinSingleton {
public:
// 检查是否已有实例运行
static bool IsRunning(const std::string& name) {
HANDLE hMutex = CreateMutexA(NULL, FALSE, name.c_str());
if (hMutex == NULL) {
throw std::runtime_error("CreateMutex failed");
}
bool alreadyExists = (GetLastError() == ERROR_ALREADY_EXISTS);
if (alreadyExists) {
CloseHandle(hMutex);
return true;
}
// 不关闭句柄,保持互斥体存在
return false;
}
WinSingleton(const std::string& name)
: m_handle(INVALID_HANDLE_VALUE)
, m_name(name)
, m_isFirstInstance(false)
{
m_handle = CreateMutexA(NULL, FALSE, name.c_str());
if (m_handle == NULL) {
throw std::runtime_error("CreateMutex failed: " + std::to_string(GetLastError()));
}
if (GetLastError() == ERROR_ALREADY_EXISTS) {
m_isFirstInstance = false;
} else {
m_isFirstInstance = true;
}
}
~WinSingleton() {
if (m_handle != INVALID_HANDLE_VALUE && m_handle != NULL) {
CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
}
// 禁止拷贝
WinSingleton(const WinSingleton&) = delete;
WinSingleton& operator=(const WinSingleton&) = delete;
bool IsFirstInstance() const { return m_isFirstInstance; }
HANDLE GetHandle() const { return m_handle; }
private:
HANDLE m_handle;
std::string m_name;
bool m_isFirstInstance;
};
// 使用示例
int main() {
try {
// 使用 Global\ 前缀确保跨会话可见
WinSingleton singleton("Global\\MyApp_SingleInstance_2024");
if (!singleton.IsFirstInstance()) {
std::cout << "程序已在运行!" << std::endl;
return 1;
}
std::cout << "程序启动成功,这是第一个实例" << std::endl;
// 主程序逻辑...
std::cout << "按回车键退出..." << std::endl;
std::cin.get();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}带窗口激活的完整示例
#include <windows.h>
#include <string>
class SingleInstanceApp {
public:
SingleInstanceApp(const std::string& mutexName, const std::string& windowClassName)
: m_mutexName(mutexName)
, m_windowClassName(windowClassName)
, m_hMutex(NULL)
, m_hwnd(NULL)
{
}
~SingleInstanceApp() {
if (m_hMutex) {
CloseHandle(m_hMutex);
}
}
// 检查并激活已有实例
bool CheckAndActivateExisting() {
m_hMutex = CreateMutexA(NULL, FALSE, m_mutexName.c_str());
if (m_hMutex && GetLastError() != ERROR_ALREADY_EXISTS) {
return false; // 第一个实例
}
// 已有实例,尝试激活其窗口
HWND hwnd = FindWindowA(m_windowClassName.c_str(), NULL);
if (hwnd) {
// 如果窗口最小化,恢复它
if (IsIconic(hwnd)) {
ShowWindow(hwnd, SW_RESTORE);
}
// 将窗口置于前台
SetForegroundWindow(hwnd);
}
return true; // 已有实例存在
}
private:
std::string m_mutexName;
std::string m_windowClassName;
HANDLE m_hMutex;
HWND m_hwnd;
};互斥体名称命名规范
| 前缀 | 作用域 | 说明 |
|---|---|---|
Global\ | 全局 | 所有用户会话可见,需要 SE_CREATE_GLOBAL_NAME 权限 |
Local\ | 当前会话 | 仅当前用户会话可见(默认) |
| 无前缀 | 当前会话 | 等同于 Local\ |
命名建议:
// 使用 GUID 或唯一标识符避免冲突
const char* mutexName = "Global\\{8A3F9B2C-1234-5678-ABCD-EF1234567890}";
// 或使用 公司名_产品名_版本号 的格式
const char* mutexName = "Global\\MyCompany_MyApp_v1.0_SingleInstance";注意事项
- 句柄生命周期:互斥体句柄必须保持打开,程序退出时自动关闭
- 命名冲突:使用唯一的名称,避免与其他程序冲突
- 权限问题:
Global\前缀需要管理员权限(Vista+) - 程序崩溃:如果程序异常退出,互斥体会被系统自动释放
- 安全性:默认安全属性允许其他进程访问,敏感场景需配置 DACL
相关 API
| 函数 | 说明 |
|---|---|
CreateMutex | 创建或打开互斥体 |
OpenMutex | 打开已存在的互斥体 |
ReleaseMutex | 释放互斥体所有权 |
WaitForSingleObject | 等待互斥体 |
CloseHandle | 关闭句柄 |