最近更新于 2023-03-11 09:41

同一时刻只能运行一个应用程序实例,一旦有多个实例运行,则后续实例会检测到先前的实例正在运行,并退出或向先前的实例发送消息。

Windows

环境

Windows SDK 10.0(Windows 11)
平台工具集:Visual Studio 2022 v143
C 语言标准:C17
字符集:Unicode

互斥锁方案

#include <Windows.h>
#include <stdio.h>

int main()
{
    HANDLE mutex = CreateMutex(NULL, TRUE, L"MyUniqueMutex");

    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        wprintf(L"已经有一个实例正在运行中!\n");
        CloseHandle(mutex);
        return -1;
    }

    //////////////////////////////
    // 在这里实现功能
    //////////////////////////////

    CloseHandle(mutex);

    return 0;
}

命名管道方案

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

int main()
{
    HANDLE hPipe;
    WCHAR pipeName[] = L"\\\\.\\pipe\\SingleInstancePipe";
    BOOL isAnotherInstanceRunning;

    hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 0, 0, NMPWAIT_USE_DEFAULT_WAIT, NULL);
    if (hPipe == INVALID_HANDLE_VALUE)
    {
        printf("创建命名管道失败 %ld\n", GetLastError());
        exit(1);
    }

    isAnotherInstanceRunning = ConnectNamedPipe(hPipe, NULL) ? FALSE : (GetLastError() == ERROR_PIPE_CONNECTED);
    if (isAnotherInstanceRunning)
    {
        printf("已经有一个实例正在运行中!\n");
        exit(0);
    }

    //////////////////////////////
    // 在这里实现功能
    //////////////////////////////
    Sleep(1000000);

    // 关闭管道
    CloseHandle(hPipe);
    return 0;
}

Linux

环境

Debian 11(arm64)
编译器:GNU(gcc)10.2.1;C 语言标准:C17;编译参数:-std=c17 -no-pie -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0

文件锁方案

文件锁方案本身对于运行权限没有要求,但是文件锁依赖文件,所以创建文件的路径就要考虑权限的问题了,一般文件锁都是放到 /var/run,但是这个路径需要 root 权限才行,所以下面的例子需要以 root 运行才可行。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>

int main()
{
    // 打开锁文件,如果文件不存在则创建它
    int lockfile = open("/var/run/myprogram.lock", O_CREAT | O_RDWR, 0644);
    if (lockfile < 0)
    {
        printf("无法打开锁文件!\n");
        return -1;
    }

    // 尝试获取文件锁
    if (flock(lockfile, LOCK_EX | LOCK_NB) < 0)
    {
        printf("已经有一个实例正在运行中!\n");
        close(lockfile);
        return -1;
    }

    //////////////////////////////
    // 在这里实现功能
    //////////////////////////////

    // 释放文件锁
    flock(lockfile, LOCK_UN);
    close(lockfile);

    return 0;
}

或者可以考虑放到家目录下,这个在当前普通用户下是有足够权限的,下面是示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>

int main()
{
    char filePath[256];
    memset(filePath, 0, sizeof(filePath));
    const char *homePath = getenv("HOME"); // 获取家目录路径
    sprintf(filePath, "%s/myprogram.lock", homePath); // 拼接锁文件路径
    // 打开锁文件,如果文件不存在则创建它
    int lockfile = open(filePath, O_CREAT | O_RDWR, 0644);
    if (lockfile < 0)
    {
        printf("无法打开锁文件!\n");
        return -1;
    }

    // 尝试获取文件锁
    if (flock(lockfile, LOCK_EX | LOCK_NB) < 0)
    {
        printf("已经有一个实例正在运行中!\n");
        close(lockfile);
        return -1;
    }

    //////////////////////////////
    // 在这里实现功能
    //////////////////////////////

    // 释放文件锁
    flock(lockfile, LOCK_UN);
    close(lockfile);

    return 0;
}

Linux 的我考虑了命名管道的方案,但是存在一个问题,如果程序是非正常结束,没有执行命名管道的删除操作,那么也无法再次运行,所以这里我没有写出来。
POSIX 信号量方案也存在这个问题,另外编译的时候要链接 Threads 多线程库,这里也没写了。
最稳妥的方案还是文件锁。