如何用C或C ++创build单个实例应用程序

为了创build一个实例应用程序,您的build议是什么?这样一次只允许一个进程运行? 文件锁,互斥锁还是什么?

一个好方法是:

#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 } 

注意,locking允许你忽略陈旧的pid文件(即你不必删除它们)。 当应用程序因任何原因终止时,操作系统将为您释放文件locking。

Pid文件不是非常有用,因为它们可能是陈旧的(文件存在,但过程不)。 因此,应用程序可执行文件本身可以被locking,而不是创build和locking一个pid文件。

更高级的方法是使用预定义的套接字名称创build和绑定unix域套接字。 绑定成功为您的应用程序的第一个实例。 同样,当应用程序由于任何原因而终止时,操作系统解除绑定套接字。 当bind()失败时,应用程序的另一个实例可以connect()并使用此套接字将其命令行parameter passing给第一个实例。

这是C ++的解决scheme。 它使用Maxim的socketsbuild议。 我喜欢这个解决scheme比基于文件的locking解决scheme更好,因为如果进程崩溃并且不会删除locking文件,则基于文件的文件会失败。 另一个用户将无法删除该文件并将其locking。 当进程退出时,套接字会自动删除。

用法:

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

避免基于文件的locking

避免基于文件的locking机制来实现应用程序的单例实例总是好的。 用户总是可以将锁文件重命名为不同的名称,然后再次运行该应用程序,如下所示:

 mv lockfile.pid lockfile1.pid 

其中lockfile.pid是在运行应用程序之前基于其检查存在的locking文件。

所以,对于只有内核直接可见的对象,最好使用lockingscheme。 所以,任何与文件系统有关的事情都是不可靠的。

所以最好的select是绑定到一个inet套接字。 请注意,unix域套接字驻留在文件系统中,并不可靠。

或者,您也可以使用DBUS来做到这一点。

对于windows,一个命名的内核对象(例如CreateEvent,CreateMutex)。 对于Unix,一个PID文件 – 创build一个文件,并写入你的进程ID。

这将取决于你想要避免哪个问题,通过强制你的应用程序只有一个实例和你考虑实例的范围。

对于deamon来说 – 通常的方法是创build一个/var/run/app.pid文件。

对于用户应用程序,我遇到了更多的应用程序问题,使得我无法运行它们两次,而不是运行两次应用程序,而这些应用程序本来不应该运行。 所以关于“为什么和在哪个范围”的答案是非常重要的,并且可能会带来关于为什么和预期范围具体的答案。

似乎没有提到 – 可以在共享内存中创build互斥锁,但需要将其标记为共享属性(未经testing):

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

也有共享内存信号量(但我没有find如何locking一个):

 int sem_id = semget(SHARED_MEMORY_KEY, 1, 0); 

你可以创build一个“匿名命名空间”AF_UNIX套接字。 这完全是Linux特有的,但具有实际上不存在文件系统的优点。

阅读unix(7)的手册页获取更多信息。

没有人提到过,但是sem_open()在现代POSIX兼容的操作系统下创build了一个真实的命名信号量。 如果给一个信号量一个初始值1,它就成为一个互斥量(只要成功获得一个锁,它就被严格释放)。

使用几个基于sem_open()的对象,可以创build所有常见的等效Windows命名对象 – 名为互斥体,命名信号量和命名事件。 命名为“manual”的事件设置为true会更难以模拟(需要四个信号量对象来正确模拟CreateEvent()SetEvent()ResetEvent() )。 无论如何,我离题了。

或者,有一个名为共享内存。 你可以用命名共享内存中的“共享进程”属性来初始化一个pthread互斥体,然后所有进程都可以在用shm_open() / mmap()打开共享内存的句柄之后安全地访问该互斥体对象。 如果sem_open()可用于您的平台,则sem_open()更容易(如果不是,则应该是出于理智的原因)。

无论使用什么方法,为了testing应用程序的单个实例,请使用wait函数的trylock()变体(例如sem_trywait() )。 如果该进程是唯一正在运行的进程,则会成功locking该互斥锁。 如果不是,它会立即失败。

不要忘记在应用程序退出时解锁并closures互斥锁。

基于maxim的答案中的提示,这里是我的双重angular色守护进程(即可以作为守护进程的一个单独的应用程序,以及作为与守护进程通信的客户机)的POSIX解决scheme。 这个scheme的优点是,当实例首先启动的时候,应该是守护进程,并且所有后续的执行都应该加载该守护进程的工作。 这是一个完整的例子,但缺乏一个真正的守护进程应该做的很多东西(例如,使用syslog进行日志logging和fork将自己置于后台 ,放弃权限等),但它已经很长,并且完全按照原样运行。 到目前为止,我只在Linux上testing过,但是IIRC应该是所有POSIX兼容的。

在这个例子中,客户端可以将传递给它们的整数作为第一个命令行参数发送,并通过套接字通过atoi进行parsing,然后将其打印到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; } 

我刚刚写了一个,经过testing。

 #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; }