14

我想防止同一长时间运行的 python 命令行脚本的多个实例同时运行,并且我希望新实例能够在新实例自杀之前将数据发送到原始实例. 我怎样才能以跨平台的方式做到这一点?

具体来说,我想启用以下行为:

  1. foo.py”是从命令行启动的,它会保持运行很长时间——几天或几周,直到机器重新启动或父进程杀死它。
  2. 每隔几分钟就会再次启动相同的脚本,但使用不同的命令行参数
  3. 启动时,脚本应该查看是否有任何其他实例正在运行。
  4. 如果其他实例正在运行,则实例#2 应将其命令行参数发送到实例#1,然后实例#2 应退出。
  5. 实例#1,如果它从另一个脚本接收命令行参数,应该启动一个新线程并(使用在上述步骤中发送的命令行参数)开始执行实例#2 将要执行的工作。

所以我在寻找两件事:一个python程序如何知道另一个自己的实例正在运行,然后一个python命令行程序如何与另一个程序通信?

更复杂的是,相同的脚本需要在 Windows 和 Linux 上运行,因此理想情况下,该解决方案将仅使用 Python 标准库,而不使用任何特定于操作系统的调用。尽管如果我需要一个 Windows 代码路径和一个 *nix 代码路径(以及if在我的代码中选择一个或另一个的大语句),但如果“相同代码”解决方案是不可能的,那也没关系。

我意识到我可能会制定一种基于文件的方法(例如,实例 #1 监视目录的更改,并且每个实例在它想要工作时将文件放入该目录)但我有点担心清理这些文件在非正常机器关闭后。理想情况下,我可以使用内存解决方案。但是我还是很灵活,如果基于持久文件的方法是唯一的方法,我愿意接受这个选项。

更多详细信息:我正在尝试这样做,因为我们的服务器正在使用一个监控工具,该工具支持运行 python 脚本来收集监控数据(例如数据库查询或 Web 服务调用的结果),然后监控工具索引这些数据以供以后使用。其中一些脚本启动起来非常昂贵,但启动后运行起来却很便宜(例如建立数据库连接与运行查询)。所以我们选择让它们在无限循环中运行,直到父进程杀死它们。

这很好用,但在较大的服务器上,可能会运行相同脚本的 100 个实例,即使它们仅每 20 分钟收集一次数据。这对 RAM、数据库连接限制等造成了严重破坏。我们希望从 100 个进程和 1 个线程切换到一个进程和 100 个线程,每个进程执行之前一个脚本正在执行的工作。

但是无法更改监视工具调用脚本的方式。我们需要保持调用相同(启动具有不同命令行参数的进程),但更改脚本以识别另一个处于活动状态,并让“新”脚本发送其工作指令(来自命令行参数)到“旧”脚本。

顺便说一句,这不是我想在一个脚本的基础上做的事情。相反,我想将此行为打包到许多脚本作者可以利用的库中——我的目标是使脚本作者能够编写不知道多实例问题的简单单线程脚本,并处理多线程和单实例。

4

4 回答 4

11

Alex Martelli 建立沟通渠道的方法是合适的。我会使用 multiprocessing.connection.Listener 来创建一个侦听器,由您选择。文档位于: http ://docs.python.org/library/multiprocessing.html#multiprocessing-listeners-clients

您可以选择在 Linux 上使用 AF_UNIX 和在 Windows 上使用 AF_PIPE,而不是使用 AF_INET(套接字)。希望一个小的“如果”不会受到伤害。

编辑:我想一个例子不会受到伤害。不过,这是一个基本的。

#!/usr/bin/env python

from multiprocessing.connection import Listener, Client
import socket
from array import array
from sys import argv

def myloop(address):
    try:
        listener = Listener(*address)
        conn = listener.accept()
        serve(conn)
    except socket.error, e:
        conn = Client(*address)
        conn.send('this is a client')
        conn.send('close')

def serve(conn):
    while True:
        msg = conn.recv()
        if msg.upper() == 'CLOSE':
            break
        print msg
    conn.close()

if __name__ == '__main__':
    address = ('/tmp/testipc', 'AF_UNIX')
    myloop(address)

这适用于 OS X,因此需要在 Linux 和(替换正确地址后)Windows 上进行测试。从安全角度来看,存在很多警告,主要是 conn.recv 解压缩其数据,因此您几乎总是使用 recv_bytes 更好。

于 2010-05-29T18:16:10.877 回答
9

The general approach is to have the script, on startup, set up a communication channel in a way that's guaranteed to be exclusive (other attempts to set up the same channel fail in a predictable way) so that further instances of the script can detect the first one's running and talk to it.

Your requirements for cross-platform functionality strongly point towards using a socket as the communication channel in question: you can designate a "well known port" that's reserved for your script, say 12345, and open a socket on that port listening to localhost only (127.0.0.1). If the attempt to open that socket fails, because the port in question is "taken", then you can connect to that port number instead, and that will let you communicate with the existing script.

If you're not familiar with socket programming, there's a good HOWTO doc here. You can also look at the relevant chapter in Python in a Nutshell (I'm biased about that one, of course;-).

于 2010-05-29T16:54:09.020 回答
1

Perhaps try using sockets for communication?

于 2010-05-29T16:50:42.320 回答
0

听起来你最好的选择是坚持使用 pid 文件,但它不仅包含进程 ID - 还包含前一个实例正在侦听的端口号。因此,在启动时检查 pid 文件,如果存在,请查看具有该 Id 的进程是否正在运行 - 如果是,则将数据发送给它并退出,否则用当前进程的信息覆盖 pid 文件。

于 2010-05-29T18:12:51.033 回答