为了创建一个单实例应用程序,您有什么建议,以便一次只允许运行一个进程?文件锁,互斥锁还是什么?
14 回答
一个好办法是:
#include <sys/file.h>
#include <errno.h>
int pid_file = open("/var/run/whatever.pid", O_CREAT | O_RDWR, 0666);
int rc = flock(pid_file, LOCK_EX | LOCK_NB);
if(rc) {
if(EWOULDBLOCK == errno)
; // another instance is running
}
else {
// this is the first instance
}
请注意,锁定允许您忽略陈旧的 pid 文件(即您不必删除它们)。当应用程序因任何原因终止时,操作系统会为您释放文件锁定。
Pid 文件不是非常有用,因为它们可能是陈旧的(文件存在但进程不存在)。因此,可以锁定应用程序可执行文件本身,而不是创建和锁定 pid 文件。
更高级的方法是使用预定义的套接字名称创建和绑定 unix 域套接字。您的应用程序的第一个实例绑定成功。同样,当应用程序因任何原因终止时,操作系统会取消绑定套接字。当bind()
失败时,应用程序的另一个实例可以connect()
并使用此套接字将其命令行参数传递给第一个实例。
这是 C++ 中的解决方案。它使用Maxim 的插座推荐。我比基于文件的锁定解决方案更喜欢这个解决方案,因为如果进程崩溃并且不删除锁定文件,基于文件的解决方案就会失败。其他用户将无法删除该文件并将其锁定。进程退出时会自动删除套接字。
用法:
int main()
{
SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
if (!singleton())
{
cerr << "process running already. See " << singleton.GetLockFileName() << endl;
return 1;
}
... rest of the app
}
代码:
#include <netinet/in.h>
class SingletonProcess
{
public:
SingletonProcess(uint16_t port0)
: socket_fd(-1)
, rc(1)
, port(port0)
{
}
~SingletonProcess()
{
if (socket_fd != -1)
{
close(socket_fd);
}
}
bool operator()()
{
if (socket_fd == -1 || rc)
{
socket_fd = -1;
rc = 1;
if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
throw std::runtime_error(std::string("Could not create socket: ") + strerror(errno));
}
else
{
struct sockaddr_in name;
name.sin_family = AF_INET;
name.sin_port = htons (port);
name.sin_addr.s_addr = htonl (INADDR_ANY);
rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
}
}
return (socket_fd != -1 && rc == 0);
}
std::string GetLockFileName()
{
return "port " + std::to_string(port);
}
private:
int socket_fd = -1;
int rc;
uint16_t port;
};
对于windows,一个命名的内核对象(例如CreateEvent、CreateMutex)。对于 unix,一个 pid 文件 - 创建一个文件并将您的进程 ID 写入其中。
您可以创建一个“匿名命名空间”AF_UNIX 套接字。这完全是 Linux 特定的,但具有实际上不必存在文件系统的优点。
阅读 unix(7) 的手册页以获取更多信息。
避免基于文件的锁定
避免使用基于文件的锁定机制来实现应用程序的单例实例总是好的。用户始终可以将锁定文件重命名为不同的名称并再次运行应用程序,如下所示:
mv lockfile.pid lockfile1.pid
lockfile.pid
在运行应用程序之前检查是否存在的锁定文件在哪里。
因此,始终最好对仅对内核直接可见的对象使用锁定方案。因此,与文件系统有关的任何事情都是不可靠的。
所以最好的选择是绑定到 inet 套接字。请注意,unix 域套接字驻留在文件系统中并且不可靠。
或者,您也可以使用 DBUS 来完成。
似乎没有提到 - 可以在共享内存中创建互斥锁,但需要将其标记为由属性共享(未测试):
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_t *mutex = shmat(SHARED_MEMORY_ID, NULL, 0);
pthread_mutex_init(mutex, &attr);
还有共享内存信号量(但我没有找到如何锁定一个):
int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);
这取决于您希望通过强制您的应用程序只有一个实例以及您考虑实例的范围来避免哪个问题。
对于守护进程——通常的方法是拥有一个/var/run/app.pid
文件。
对于用户应用程序,我遇到的应用程序问题更多,这些问题阻止我运行它们两次,而不是能够运行两次不应该运行的应用程序。因此,关于“为什么以及在哪个范围内”的答案非常重要,并且可能会针对原因和预期范围带来具体的答案。
没有人提到它,但sem_open()
在现代兼容 POSIX 的操作系统下创建了一个真正的命名信号量。如果你给一个信号量一个初始值1,它就变成了一个互斥体(只要在成功获得锁的情况下才严格释放它)。
使用多个sem_open()
基于 的对象,您可以创建所有常见的等效 Windows 命名对象 - 命名互斥体、命名信号量和命名事件。将“手动”设置为 true 的命名事件有点难以模拟(它需要四个信号量对象才能正确模拟CreateEvent()
、SetEvent()
和ResetEvent()
)。无论如何,我离题了。
或者,有命名共享内存。shm_open()
您可以在命名共享内存中使用“共享进程”属性初始化 pthread 互斥锁,然后在使用/打开共享内存的句柄后,所有进程都可以安全地访问该互斥锁对象mmap()
。 sem_open()
如果它可用于您的平台,则更容易(如果不是,它应该是为了理智起见)。
无论您使用哪种方法,要测试应用程序的单个实例,请使用trylock()
等待函数的变体(例如sem_trywait()
)。如果进程是唯一运行的进程,它将成功锁定互斥锁。如果不是,它将立即失败。
不要忘记在应用程序退出时解锁和关闭互斥锁。
根据这里maxim 的回答中的提示,我的双角色守护进程的 POSIX 解决方案(即可以充当守护进程并作为与该守护进程通信的客户端的单个应用程序)。该方案的优点是,当首先启动的实例应该是守护进程并且所有后续执行应该只是加载该守护进程的工作时,该方案提供了一个优雅的问题解决方案。这是一个完整的例子,但缺少很多真正的守护进程应该做的事情(例如,syslog
用于日志记录和fork
正确地将自己置于后台,删除权限等),但它已经很长并且完全按原样工作。到目前为止,我只在 Linux 上对此进行了测试,但 IIRC 它应该是所有 POSIX 兼容的。
在示例中,客户端可以将整数作为第一个命令行参数传递给它们,并atoi
通过套接字将其解析到守护进程,该守护进程将其打印到stdout
. 使用这种套接字,还可以传输数组、结构甚至文件描述符(请参阅 参考资料man 7 unix
)。
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_NAME "/tmp/exampled"
static int socket_fd = -1;
static bool isdaemon = false;
static bool run = true;
/* returns
* -1 on errors
* 0 on successful server bindings
* 1 on successful client connects
*/
int singleton_connect(const char *name) {
int len, tmpd;
struct sockaddr_un addr = {0};
if ((tmpd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
printf("Could not create socket: '%s'.\n", strerror(errno));
return -1;
}
/* fill in socket address structure */
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
int ret;
unsigned int retries = 1;
do {
/* bind the name to the descriptor */
ret = bind(tmpd, (struct sockaddr *)&addr, len);
/* if this succeeds there was no daemon before */
if (ret == 0) {
socket_fd = tmpd;
isdaemon = true;
return 0;
} else {
if (errno == EADDRINUSE) {
ret = connect(tmpd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
if (ret != 0) {
if (errno == ECONNREFUSED) {
printf("Could not connect to socket - assuming daemon died.\n");
unlink(name);
continue;
}
printf("Could not connect to socket: '%s'.\n", strerror(errno));
continue;
}
printf("Daemon is already running.\n");
socket_fd = tmpd;
return 1;
}
printf("Could not bind to socket: '%s'.\n", strerror(errno));
continue;
}
} while (retries-- > 0);
printf("Could neither connect to an existing daemon nor become one.\n");
close(tmpd);
return -1;
}
static void cleanup(void) {
if (socket_fd >= 0) {
if (isdaemon) {
if (unlink(SOCKET_NAME) < 0)
printf("Could not remove FIFO.\n");
} else
close(socket_fd);
}
}
static void handler(int sig) {
run = false;
}
int main(int argc, char **argv) {
switch (singleton_connect(SOCKET_NAME)) {
case 0: { /* Daemon */
struct sigaction sa;
sa.sa_handler = &handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) != 0 || sigaction(SIGQUIT, &sa, NULL) != 0 || sigaction(SIGTERM, &sa, NULL) != 0) {
printf("Could not set up signal handlers!\n");
cleanup();
return EXIT_FAILURE;
}
struct msghdr msg = {0};
struct iovec iovec;
int client_arg;
iovec.iov_base = &client_arg;
iovec.iov_len = sizeof(client_arg);
msg.msg_iov = &iovec;
msg.msg_iovlen = 1;
while (run) {
int ret = recvmsg(socket_fd, &msg, MSG_DONTWAIT);
if (ret != sizeof(client_arg)) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
printf("Error while accessing socket: %s\n", strerror(errno));
exit(1);
}
printf("No further client_args in socket.\n");
} else {
printf("received client_arg=%d\n", client_arg);
}
/* do daemon stuff */
sleep(1);
}
printf("Dropped out of daemon loop. Shutting down.\n");
cleanup();
return EXIT_FAILURE;
}
case 1: { /* Client */
if (argc < 2) {
printf("Usage: %s <int>\n", argv[0]);
return EXIT_FAILURE;
}
struct iovec iovec;
struct msghdr msg = {0};
int client_arg = atoi(argv[1]);
iovec.iov_base = &client_arg;
iovec.iov_len = sizeof(client_arg);
msg.msg_iov = &iovec;
msg.msg_iovlen = 1;
int ret = sendmsg(socket_fd, &msg, 0);
if (ret != sizeof(client_arg)) {
if (ret < 0)
printf("Could not send device address to daemon: '%s'!\n", strerror(errno));
else
printf("Could not send device address to daemon completely!\n");
cleanup();
return EXIT_FAILURE;
}
printf("Sent client_arg (%d) to daemon.\n", client_arg);
break;
}
default:
cleanup();
return EXIT_FAILURE;
}
cleanup();
return EXIT_SUCCESS;
}
我刚刚写了一个,并测试了。
#define PID_FILE "/tmp/pidfile"
static void create_pidfile(void) {
int fd = open(PID_FILE, O_RDWR | O_CREAT | O_EXCL, 0);
close(fd);
}
int main(void) {
int fd = open(PID_FILE, O_RDONLY);
if (fd > 0) {
close(fd);
return 0;
}
// make sure only one instance is running
create_pidfile();
}
只需在单独的线程上运行此代码:
void lock() {
while(1) {
ofstream closer("myapplock.locker", ios::trunc);
closer << "locked";
closer.close();
}
}
将此作为您的主要代码运行:
int main() {
ifstream reader("myapplock.locker");
string s;
reader >> s;
if (s != "locked") {
//your code
}
return 0;
}
这是一个基于 sem_open 的解决方案
/*
*compile with :
*gcc single.c -o single -pthread
*/
/*
* run multiple instance on 'single', and check the behavior
*/
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>
#define SEM_NAME "/mysem_911"
int main()
{
sem_t *sem;
int rc;
sem = sem_open(SEM_NAME, O_CREAT, S_IRWXU, 1);
if(sem==SEM_FAILED){
printf("sem_open: failed errno:%d\n", errno);
}
rc=sem_trywait(sem);
if(rc == 0){
printf("Obtained lock !!!\n");
sleep(10);
//sem_post(sem);
sem_unlink(SEM_NAME);
}else{
printf("Lock not obtained\n");
}
}
对不同答案的评论之一说“我发现 sem_open() 相当缺乏”。我不确定缺少的细节
所有学分都归马克·拉卡塔所有。我只是做了一些非常小的修饰而已。
主文件
#include "singleton.hpp"
#include <iostream>
using namespace std;
int main()
{
SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
if (!singleton())
{
cerr << "process running already. See " << singleton.GetLockFileName() << endl;
return 1;
}
// ... rest of the app
}
单例.hpp
#include <netinet/in.h>
#include <unistd.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <stdexcept>
using namespace std;
class SingletonProcess
{
public:
SingletonProcess(uint16_t port0)
: socket_fd(-1)
, rc(1)
, port(port0)
{
}
~SingletonProcess()
{
if (socket_fd != -1)
{
close(socket_fd);
}
}
bool operator()()
{
if (socket_fd == -1 || rc)
{
socket_fd = -1;
rc = 1;
if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
throw std::runtime_error(std::string("Could not create socket: ") + strerror(errno));
}
else
{
struct sockaddr_in name;
name.sin_family = AF_INET;
name.sin_port = htons (port);
name.sin_addr.s_addr = htonl (INADDR_ANY);
rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
}
}
return (socket_fd != -1 && rc == 0);
}
std::string GetLockFileName()
{
return "port " + std::to_string(port);
}
private:
int socket_fd = -1;
int rc;
uint16_t port;
};
#include <windows.h>
int main(int argc, char *argv[])
{
// ensure only one running instance
HANDLE hMutexH`enter code here`andle = CreateMutex(NULL, TRUE, L"my.mutex.name");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
return 0;
}
// rest of the program
ReleaseMutex(hMutexHandle);
CloseHandle(hMutexHandle);
return 0;
}
从:这里