概述:系统锁利用命名互斥体(Named Mutex)实现进程互斥,常用于确保程序只能运行一个实例。

[toc]

本文部分内容由 AI 生成,经人工修订。

系统锁原理

系统锁的核心是使用 CreateMutex 创建一个命名互斥体。命名的内核对象在 Windows 系统中是全局可见的,因此:

  1. 首次创建时,成功返回互斥体句柄
  2. 再次使用相同名称创建时,系统会返回已存在的句柄,并通过 GetLastError() 返回 ERROR_ALREADY_EXISTS
  3. 利用这一特性,可以实现进程单实例运行

核心函数

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";

注意事项

  1. 句柄生命周期:互斥体句柄必须保持打开,程序退出时自动关闭
  2. 命名冲突:使用唯一的名称,避免与其他程序冲突
  3. 权限问题Global\ 前缀需要管理员权限(Vista+)
  4. 程序崩溃:如果程序异常退出,互斥体会被系统自动释放
  5. 安全性:默认安全属性允许其他进程访问,敏感场景需配置 DACL

相关 API

函数说明
CreateMutex创建或打开互斥体
OpenMutex打开已存在的互斥体
ReleaseMutex释放互斥体所有权
WaitForSingleObject等待互斥体
CloseHandle关闭句柄

参考链接