最近更新于 2024-05-05 14:18
同一时刻只能运行一个应用程序实例,一旦有多个实例运行,则后续实例会检测到先前的实例正在运行,并退出或向先前的实例发送消息。
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 多线程库,这里也没写了。
最稳妥的方案还是文件锁。