有些程序我们希望在一台机器上只有一个实例在运行,我在windows下也遇到过很多类似这样的程序,如QQ,它只允许同时运行一个。那么我们在Linux该如何实现这样的单例运行的程序呢?

思路

实现这样的程序方法很多,但是总体思路都是类似的:

  • 1.启动程序,检测标志,判断是否有同样的程序运行,是则2,否则3

  • 2.程序退出

  • 3.程序启动,并设置标志,以便下次启动时检测

实现方法

按照这种思路,实现的方法有很多种,例如使用ps等命令获取该进程的进程数,大于0 表示已有运行;启动后写一个临时文件,如果下次启动时发现有该文件,则直接退出;创建一个文件并加锁,退出时删除文件,新的程序启动时试图加锁,如果失败,则说明已有实例运行……

除了上面说到的这些,可能还有一些其他的实际做法,但是本文介绍一种实用并且也是非常通用的做法,即文件锁的方法。

基本原理

程序在启动后,打开一个program.pid文件(无则创建),然后试图去设置文件锁(如果还不理解锁的概念,可以简单理解为,一旦a写锁定了,b就无法进一步写操作了,除非a释放锁),如果设置成功,就将该程序的进程ID写入该文件;如果加锁失败,那么说明已经有另外一个实例在运行了,则退出此次启动。而当前已经运行的程序如果退出了,该文件会自动解除锁定。

实际上,我们观察一下/var/run/目录下,有很多类似这样的文件:

 

$ ls -l /var/run/*.pid
-rw-r--r-- 1 root root 5 11月 24 08:19 /var/run/acpid.pid
-rw-r--r-- 1 root root 5 11月 24 08:19 /var/run/atd.pid
-rw-r--r-- 1 root root 5 11月 24 08:19 /var/run/crond.pid
-rw-r--r-- 1 root root 5 11月 24 09:08 /var/run/dhclient-wlp3s0.pid
-rw-r--r-- 1 root root 4 11月 24 08:19 /var/run/docker.pid

不过这个位置通常只有root用户能够写入。

实现

在看代码实现之前,先看下文件锁(记录锁)和fcntl函数。
flock结构至少包含以下字段:

 

struct flock {
    short l_type; /*锁类型 F_RDLCK,F_WRLCK, F_UNLCK*/
    short l_whence;  /* 偏移开始的位置 SEEK_SET, SEEK_CUR, SEEK_END */
    off_t l_start;   /* 开始锁定区域*/
    off_t l_len;     /* 锁定字节数 */
    pid_t l_pid;     /* 记录锁的进程ID*/
};

从结构体成员可以看到,它不仅可以锁整个文件,还可以锁某个区域,但是本文仅用于锁定整个文件。

 

int fcntl(int fd, int cmd, ... /* arg */

fcntl函数的cmd选项也很多,本文只用到F_SETLK(或F_SETLKW,),即设置锁。
结合以上两者,参考代码如下:

 

//来源:公众号【编程珠玑】
//博客:https://www.yanbinghu.com
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#define PROC_NAME "single_instance"
#define PID_FILE_PATH "/var/run/"
static int lockFile(int fd);
static int isRunning(const char *procname);
int main(void)
{
    /*判断是否已经有实例在运行*/
    if(isRunning(PROC_NAME))
    {
        exit(-1);
    }
    printf("run ok\n");
    sleep(20);//避免程序立即退出
    return 0;
}
/*锁文件还可以使用flock,目的是类似的。不过是它是BSD系统调用,并且某些版本不支持NFS,出于移植性考虑,使用fcntl*/
static int lockFile(int fd)
{
    struct flock fl;
    fl.l_type   = F_WRLCK;//设置写锁
    fl.l_start  = 0;
    fl.l_whence = SEEK_SET;
    fl.l_len    = 0;
    fl.l_pid = -1;//锁定文件,设置为-1

return(fcntl(fd, F_SETLK, &fl));
}

static int isRunning(const char *procname)
{
    char buf[16] = {0};
    char filename[128] = {0};
    sprintf(filename, "%s%s.pid",PID_FILE_PATH,procname);
    int fd = open(filename, O_CREAT|O_RDWR );//可读可写,不存在时创建
    if (fd < 0) {
        printf("open file %s failed!\n", filename);
        return 1;
    }
    if (-1 == lockFile(fd)) /*尝试加锁*/
    {                                                  
        printf("%s is already running\n", procname);
        close(fd);
        return 1;
    } 
    else 
    {
        ftruncate(fd, 0);/*截断文件,重新写入pid*/
        sprintf(buf, "%ld", (long)getpid());
        write(fd, buf, strlen(buf) + 1);
        return 0;
    }
}

编译运行

 

$ gcc -o single_instance single_instance.c
$ ./single_instance  #注意root权限运行,或者调整pid文件位置
run ok

查看pid文件目录下已经有了pidfile:

 

$ ls -al /var/run/single_instance.pid
-rw-r--r-- 1 root root 6 11月 24 11:36 /var/run/single_instance.pid

在另外一个终端再次尝试运行:

 

$ ./single_instance
single_instance is already running

如果你想控制同一个目录下的bin文件只能运行一个,那么可以设置pid文件的位置为当前目录。

这种方式有什么特点呢:

  • 简单可靠

  • 可读可见,相比于信号量或共享内存,它更容易观察

  • 无性能要求,启动时加锁,结束释放。

  • 一旦出现异常没有释放,也可以手动删除文件

当然对于BSD系统,还可以使用下面的接口来完成:

 

struct pidfh *
     pidfile_open(const char *path, mode_t mode, pid_t *pidptr);
int pidfile_write(struct pidfh *pfh);

int pidfile_close(struct pidfh *pfh);

int pidfile_remove(struct pidfh *pfh);

本文就不过多介绍了。

总结

单例运行的基本原理是类似的,而pid文件是一种常见的单例运行的方式,很多知名的开源组件都是使用类似的方式。对于shell脚本,还可以使用flock命令进行类似的操作。

你一般用什么方式来实现单例运行呢?欢迎留言分享。

https://www.yanbinghu.com/2019/11/28/46317.html

相关精彩推荐

如何优雅地将printf的打印保存在文件中?

为何优先选用unique_ptr而不是裸指针?

为什么执行自己的程序要在前面加./

关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源

如何让你的程序同时只能运行一个?相关推荐

  1. 让powershell同时只能运行一个脚本(进程互斥例子)

    powershell,mutex,互斥,进程互斥,脚本互斥 powershell脚本互斥例子,在powershell类别文章中,声明原创唯一. powershell 传教士 原创文章 2016-07- ...

  2. 同一个PC只能运行一个应用实例(考虑多个用户会话情况)

    原文:同一个PC只能运行一个应用实例(考虑多个用户会话情况) 1 class Program 2 { 3 private static Mutex m; 4 5 [STAThread] 6 stati ...

  3. 解决C#程序只允许运行一个实例的几种方法详解

    解决C#程序只允许运行一个实例的几种方法详解 参考文章: (1)解决C#程序只允许运行一个实例的几种方法详解 (2)https://www.cnblogs.com/randyzhuwei/p/5403 ...

  4. python打开另一个程序_python 新手问题啊,为什么我运行一个python程序,另一个就会自动退出呢?始终只能运行一个...

    /data/python/caiji_asc.py /data/python/caiji_desc.py 两个文件的代码几乎一样的,就是查询mysql的时候order 排序不一样 下面是具体代码,刚学 ...

  5. 让你的程序只能运行一个实例

        先说一个循环系统进程的方法吧,             //首先取得你的程序当前进程             Process current = Process.GetCurrentProce ...

  6. C#程序只允许运行一个实例的解决方案

    最近在做winform的程序中,需要只能打开一个程序,如果已经存在,则激活该程序的窗口,并显示在最前端.在网上google了一哈,找到了很多的解决方案.这里我整理了3种方案,并经过了测试,现和朋友们分 ...

  7. C++程序只允许运行一个实例

    函数原型: HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,//必须为NULLBOOL bInitialOwner, //表示m ...

  8. python线程只能启动一次_python多线程只能运行一个线程的问题

    问题描述: 使用 python threading.Thread() 建立两个线程,启动后只有线程1在运行,线程2不运行. 问题代码 import time, threading def run_th ...

  9. java类内存中只能运行一个实例对象

    方法:构造方法私有化(private) public class TestClass { private static TestClass obj = new TestClass (); //私有构造 ...

最新文章

  1. 人大附中高中生学Python获数据挖掘竞赛一等奖,将去旷视科技实习
  2. 2021年,作为算法工程师的你们会在CV业务落地上用Transformer吗?
  3. 【MCtalk讨论】 短视频平台出路在何方?
  4. JS的编码:escape,encodeURI,encodeURIComponent,解码:unescape,decodeURI,decodeURIComp
  5. UVA 11452 Dancing the Cheeky-Cheeky
  6. Spring-Jpa : @MappedSuperclass的作用
  7. SpringBoot 优雅停止服务的几种方法
  8. 工作中用到的sh脚本(持续更新)
  9. FTA故障树分析法-DFMEA的另外一张脸
  10. Electron+Vue3+TypeScript+Vite桌面应用程序项目初始化
  11. 进入Mysql数据库操作
  12. GT-suite v2016的下载和安装
  13. 阿里又起社交心,天天动听做音乐轻社交?
  14. 制作u盘winpe启动盘_Ventoy启动盘制作工具,Win、Linux、PE多系统镜像装进同1个U盘...
  15. 反射之前奏Oracle升级版
  16. 源代码转换成图-控制流图、数据流图
  17. HC05蓝牙模块AT指令与手机蓝牙控制STM32板载LED
  18. 重磅 I IT4IT 2.1中文版正式发布特邀专家彭斐推荐
  19. 最新在线客服系统php代码微信软件公众号小程序app二维码聊天网站源码
  20. SharePoint 2013 Search KQL 介绍及实例分析

热门文章

  1. 如何减小电压跟随器输出电阻_逐次比较式模数转换器如何获取最佳采样频率
  2. python 获取qq群成员信息_用Python编写工具获取QQ群成员的昵称和号码,使用,及,小...
  3. PTA L1-088 静静的推荐
  4. 攻击者思维 - iOS 摄像头指示灯不亮,就真的没事了吗?
  5. 阿里巴巴开源力作(二)--分布式流量卫兵Sentinel简介及控制台安装
  6. 计算机检测不到蓝牙,图解Win10 1809系统中检测不到蓝牙设备的方法
  7. 我是谁:没有绝对安全的系统黑客手法介绍
  8. Android流星雨效果---史上最炫,浪漫,值得陪你女朋友一起看~ [捂脸]
  9. 下载Synechococcus elongatus UTEX 2973(accession no.为GCA_000817325.1 )的基因组注释文件,统计其中染色体序列(CP006471.1)前10
  10. Web前端低代码介绍的ppt大纲