如何防止Linux程序多次运行?

16

如何防止Linux程序/守护进程在给定时间内被执行多次的最佳方法?

7个回答

25

最常见的方法是创建一个PID文件:定义一个文件存储路径(通常在/var/run内部)。在成功启动时,您需要将进程的PID写入该文件。当您决定是否要启动时,读取该文件并检查所引用的进程是否存在(或者如果存在,则不是您守护进程的实例:在Linux上,您可以查看/proc/$PID/exe)。在关闭时,您可以删除该文件,但这不是严格必需的。

有一些脚本可供使用,您可能会发现start-stop-daemon很有用:它可以使用PID文件,甚至只检查全局可执行文件的存在性。它正是为了这个任务而设计的,并且旨在帮助人们正确地完成这项工作。


通常最好在这样的 pid 文件上使用 flock,以确保使用 pid 的进程确实属于您。 - Hasturkun
把独占锁放在pid文件上,然后将其留在那里。如果锁定失败,则表示另一个实例正在运行。 - MarkR
这取决于您是否使用守护进程本身编写PID文件:如果您正在使用start-stop-daemon或类似工具,则无法以此方式锁定文件。此外,即使您设法获得锁定,仍应检查文件中包含的PID,因此我不确定它对您有多大帮助。 - Andrew Aylett

6
使用 boost interprocess library创建一个将由进程创建的内存块。如果它已经存在,则表示有另一个进程实例。退出。
更精确的链接是这个
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/scoped_ptr.hpp>

int main()
{
  using boost::interprocess;
  boost::scoped_ptr<shared_memory_object> createSharedMemoryOrDie;
  try
  {
     createSharedMemoryOrDie.reset( 
       new shared_memory_object(create_only, "shared_memory", read_write));
  } catch(...)
  {
     // executable is already running
     return 1; 
  }

  // do your thing here
}

2
如果在释放 shared_memory_object 之前使用 -9 杀死了进程,这样做还有效吗? - rustyx
如果在进程释放共享内存对象之前使用 -9 杀死进程,这个方法还能起作用吗? - abhiarora

5

如果您有代码访问权限(即正在编写代码):

  • 创建临时文件,锁定它,在完成后删除,如果文件存在,则返回1;或者,
  • 列出进程,如果进程名称在列表中,则返回1;

如果您没有访问权限:

  • 创建一个启动器包装程序来执行以上操作之一。

好的,听起来很有趣。但是如果在删除临时文件之前进程被终止了怎么办? - Jan Deinhard
那是一个问题,没错。不过你可以使用第二种方法来避免这个问题。 - Delan Azabani
或者有一个普通的文件,比如 application.pid,用于获取独占锁,在应用程序结束时会被释放。 - mhitza

3
我认为这个方案应该可行(而且也能够防止崩溃):
前置条件:你的应用程序有一个PID文件(通常在/var/run/目录下)。
1. 尝试打开PID文件。
2. 如果不存在,则创建它并将你的PID写入其中。继续进行程序的其他部分。
3. 如果已经存在,则读取PID。
4. 如果PID仍在运行 是你的程序的实例,则退出。
5. 如果PID不存在或由其他程序使用,则删除PID文件并转到步骤2。
6. 在程序终止时,删除PID文件。
步骤5中的循环确保如果同时启动了两个实例,则最终只有一个实例在运行。

如果pid文件中没有您的pid,则不要删除它,因为这可能会引入竞争条件。 - MarkR

2

我不知道你确切的要求是什么,但我有类似的需求;在那种情况下,我从一个Shell脚本启动了我的守护进程(这是一台HP-UX机器),在启动守护进程之前,我检查是否已经有同名的exec在运行。如果有,则不启动新的。

通过这种方式,我还能够控制进程实例的数量。


你如何检查“同名的执行文件是否已经在运行中”? - phunehehe
您可以使用“ps”命令列出正在运行的进程。至少在HP-UX中是这样。 - Vaibhav
1
我有一个像这样工作的脚本。它非常有用,但是要可靠地做到这一点是不可能的。例如,如果exec是一个脚本,你将看到运行的是解释器(sh、perl或其他)和脚本的名称是第一个参数。但也有可能使用虚假名称执行程序,在这种情况下,您实际上无法知道如何识别它。您还可以查看/proc文件系统。但是没有办法使其100%可靠。 - reinierpost

0

有一个pid文件,在启动时执行'kill -0 <pid>'。其中<pid>是从文件中读取的值。如果响应!= 0,则守护程序未运行,您可能需要重新启动它

另一种方法是绑定到端口,并在第二次尝试启动守护进程时处理绑定异常。如果端口正在使用,则退出,否则继续运行守护进程。


3
kill -0 命令只能告诉你进程是否存在,但无法确定该进程是否是你的守护程序。我曾经遇到过一个非常恼人的 bug,在启动时进程 ID 号是连续的情况下,另一个进程占用了同一个进程 ID 号,导致守护程序错误地启动失败。 - MarkR

-1

我相信我的解决方案是最简单的:

(如果竞态条件是可能发生的情况,请不要使用它,但在任何其他情况下,这是一个简单而令人满意的解决方案)

#include <sys/types.h>
#include <unistd.h>
#include <sstream>

void main()
{
    // get this process pid
    pid_t pid = getpid();

    // compose a bash command that:
    //    check if another process with the same name as yours
    //    but with different pid is running
    std::stringstream command;
    command << "ps -eo pid,comm | grep <process name> | grep -v " << pid;
    int isRuning = system(command.str().c_str());
    if (isRuning == 0) {
        cout << "Another process already running. exiting." << endl;
        return 1;
    }
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接