如何在C或C ++中创建单个实例应用程序

时间:2011-03-17 12:49:07

标签: c++ c linux single-instance

为了创建单个实例应用程序,您有什么建议,以便一次只允许一个进程运行?文件锁,互斥还是什么?

12 个答案:

答案 0 :(得分:58)

一个好方法是:

#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()并使用此套接字将其命令行参数传递给第一个实例。

答案 1 :(得分:11)

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

答案 2 :(得分:5)

避免基于文件的锁定

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

mv lockfile.pid lockfile1.pid

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

因此,对于仅对内核直接可见的对象,最好使用锁定方案。因此,任何与文件系统有关的事情都是不可靠的。

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

或者,您也可以使用DBUS。

答案 3 :(得分:4)

对于windows,一个命名的内核对象(例如CreateEvent,CreateMutex)。对于unix,一个pid文件 - 创建一个文件并将进程ID写入其中。

答案 4 :(得分:3)

这取决于您希望通过强制您的应用程序只有一个实例和您考虑实例的范围来避免哪个问题。

对于守护进程 - 通常的方法是拥有/var/run/app.pid文件。

对于用户应用程序,我遇到了更多的应用程序问题,这些问题阻止我运行它们两次,而不能运行两次不应该运行的应用程序。因此,关于“为什么以及在哪个范围内”的答案非常重要,并且可能会针对原因和预期范围提供具体答案。

答案 5 :(得分:3)

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

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

答案 6 :(得分:2)

您可以创建一个“匿名命名空间”AF_UNIX套接字。这完全取决于Linux,但其优点是实际上不存在文件系统。

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

答案 7 :(得分:2)

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

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

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

无论您使用哪种方法,要测试应用程序的单个实例,请使用等待函数的trylock()变体(例如sem_trywait())。如果进程是唯一运行的进程,则它将成功锁定互斥锁。如果不是,它将立即失败。

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

答案 8 :(得分:0)

基于maxim's answer中的提示,这是我的双角色守护进程的POSIX解决方案(即可以充当守护进程的单个应用程序以及与该守护进程通信的客户端)。这个方案的优点是,当首先启动的实例应该是守护进程时,提供优雅的问题解决方案,并且所有后续执行应该只是加载该守护进程的工作。这是一个完整的例子,但缺少一个真正的守护进程应该做的很多事情(例如使用syslog for logging and fork to put itself into background correctly,删除特权等),但它已经很长并且完全按原样工作。到目前为止我只在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;
}

答案 9 :(得分:0)

我刚刚写了一个,并经过测试。

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

答案 10 :(得分:0)

只需在单独的线程上运行此代码:

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

答案 11 :(得分: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()相当缺乏”。我不确定缺少的具体细节