• Linux C/C++ 单实例进程设计


    Linux C/C++ 单实例进程设计

    概述

    当我们使用 C/C++ 开发一个守护进程或者一个服务端程序的时候,有时需要将该程序变成单进程,防止重复打开 socket 端口或者提供重复的服务。如果某个进程同时有多个实例运行,那么每个实例都可能尝试打开同一个端口或执行某个预定的操作,于是造成该操作的重复执行,这很可能导致出错。

    POSIX 系统中可以使用文件和记录锁机制来达成单实例进程设计。

    文件和记录锁机制为一种方法提供了基础,该方法保证一个守护进程只有一个副本在运行。如果每一个守护进程创建一个固定名字的文件,并在该文件的整体上加一把写锁,那么只允许创建一把这样的写锁。在此之后创建写锁的尝试都会失败,这向后续进程副本指明已有一个副本正在运行。

    文件和记录锁提供了一种方便的互斥机制。如果守护进程在一个文件的整体上得到一把写锁,那么在该守护进程终止时,这把锁将被自动删除。这就简化了复原所需的处理,去除了对以前的守护进程实例需要进行清理的有关操作。
    ——《UNIX 环境高级编程(第 3 版)》

    示例

    #include       // included for `fprintf`
    #include      // included for `open`
    #include       // included for `errno`
    #include      // inlcuded for `strerror`
    #include      // included for `exit`
    #include    // included for `flock`
    
    #define PIDFILE "/var/run/hello.pid"
    
    int isRunning(const char *pidfile) {
        int fd = -1;
        char buf[16] = { 0x00 };
    
        if (-1 == (fd = open(pidfile, O_RDWR | O_CREAT, 0666))) {
            fprintf(stderr, "can't open or create %s: %s\n",
                    pidfile, strerror(errno));
            exit(1);
        }
    
        if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
            int save_errno = errno;
            if ((save_errno == EAGAIN) || (save_errno == EACCES)) {
                close(fd);
                return 1;
            }
    
            fprintf(stderr, "can't lock %s: %s\n", pidfile, strerror(save_errno));
            exit(1);
        }
        ftruncate(fd, 0);
        snprintf(buf, sizeof(buf) - 1, "%ld", (long)getpid());
        write(fd, buf, strlen(buf) + 1);
        return 0;
    }
    
    int main(int argc, char *argv[]) {
        if (isRunning(PIDFILE)) {
            fprintf(stderr, "another instance is already running.\n");
            exit(1);
        }
        
        while (1) {
            pause();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    每个进程都将尝试创建一个文件并将自己的 PID 号写入到文件中,如果文件已经上锁,那么 flock 函数就会失败并返回 EAGAIN 错误码。

    在上述示例中,isRunning 函数返回 1 表明已经有另一个实例进程在运行。否则将使用 ftruncate 函数将文件长度截断为 0, 将进程 ID (通过 getpid 接口获得) 写入该文件。

    函数说明

    #include 
    
    int flock(int fd, int operation);
    
    • 1
    • 2
    • 3

    其中 fd 是要操作的文件描述符。operation 可以是以下的值:

    • LOCK_SH —— Shared lock 共享锁,在给定时间,多个进程可能持有给定文件的共享锁。
    • LOCK_EX —— Exclusive lock 独占锁,在给定的时间,只有一个进程可以持有给定文件的独占锁。
    • LOCK_UN —— Unlock 删除此进程所持有的已存在的锁。

    如果另一个进程持有不兼容的锁,则对 flock() 的调用可能会阻塞。要发出非阻塞请求,请将 LOCK_NB(通过“或”运算)与上述任何 operation 一起使用。

    单个文件不能同时具有共享和独占锁。

    编译运行

    在命令行执行 gcc 进行编译:

    gcc -Wall -Werror -o hello hello.c
    
    • 1

    因为 pid 文件指定在 /var/run 目录,所以需要 root 权限执行,否则会报 can't open or create /var/run/hello.pid: Permission denied 错误。

    sudo ./hello
    
    • 1

    进程启动后,可以看到创建了 /var/run/hello.pid 文件。当再启动一个终端执行 hello 进程时将会得到报错信息:

    sudo ./hello
    another instance is already running.
    
    • 1
    • 2

    因为已经有另一个进程实例在运行,所以通过文件和记录锁机制就能够达到单实例进程的目的。

    参考资料

    [1] 《UNIX 环境高级编程(第 3 版)》13.5 单实例守护进程
    [2] QNX 7.1 交叉编译 cron

    欢迎关注我的公众号:飞翔的小黄鸭
    也许会发现不一样的风景


    △ \triangle Linux C/C++ 处理命令行参数
    ▽ \bigtriangledown Linux C/C++ 获取系统时间

  • 相关阅读:
    腾讯 32k16 薪和美团 35k15.5 薪,有点顾虑双offer到底该选哪个?
    Spring事件监听机制
    微信小程序 java高校某学院通知与文件分享系统Android app
    springboot盲盒售卖
    Tungsten Fabric Rabbitmq故障处理
    重磅发布!汉威科技燃气安全一站式解决方案全面覆盖燃气安全最新需求
    VLAN实验
    vue3+vite项目中使用svgIcon
    【PyTorch】深度学习实践之 用Softmax和CrossEntroyLoss解决多分类问题(Minst数据集)
    C++类和对象2
  • 原文地址:https://blog.csdn.net/bluebird_shao/article/details/128140252