检测进程是否已在运行并与之协作
我正在尝试创建一个程序,这个程序可以启动一个进程池,比如说5个进程,执行一些操作,然后退出,但这5个进程保持打开状态。之后用户可以再次运行这个程序,这次它不会启动新的进程,而是使用已经存在的5个进程。基本上,这是一个生产者-消费者模型,其中:
- 生产者的数量是变化的。
- 消费者的数量是固定的。
- 生产者可以由不同的程序或不同的用户在不同的时间启动。
我正在使用内置的 multiprocessing
模块,目前在 Python 2.6.4 中,但我打算最终迁移到 3.1.1。
以下是一个基本的使用场景:
- 开始状态 - 没有进程在运行。
- 用户启动
program.py operation
- 启动一个生产者,五个消费者在运行。 - 操作完成 - 五个消费者在运行。
- 用户再次启动
program.py operation
- 启动一个生产者,五个消费者在运行。 - 用户再次启动
program.py operation
- 启动两个生产者,五个消费者在运行。 - 操作完成 - 一个生产者,五个消费者在运行。
- 操作完成 - 五个消费者在运行。
- 用户启动
program.py stop
并完成 - 没有进程在运行。 - 用户启动
program.py start
并完成 - 五个消费者在运行。 - 用户启动
program.py operation
- 启动一个生产者,五个消费者在运行。 - 操作完成 - 五个消费者在运行。
- 用户启动
program.py stop
并完成 - 没有进程在运行。
我遇到的问题是,我不知道从哪里开始:
- 检测消费者进程是否在运行。
- 如何从一个之前没有关系的程序访问这些进程。
- 如何以跨平台的方式完成第1和第2点。
一旦我能做到这些,我就知道如何管理这些进程。必须有一些可靠的方法来检测现有的进程,因为我看到 Firefox 做到了这一点,以防止多个 Firefox 实例同时运行,但我不知道如何在 Python 中实现。
3 个回答
看看这些不同的服务发现机制:http://en.wikipedia.org/wiki/Service_discovery
基本的想法是,当消费者(使用服务的程序)启动时,它们会注册自己的服务。然后,生产者(提供服务的程序)在启动时会进行发现过程。如果它找到消费者,就会和他们连接。如果找不到,就会启动新的消费者。在大多数这样的系统中,服务通常还可以发布一些属性,这样每个消费者就能独特地识别自己,并向发现的生产者提供其他信息。
Bonjour/zeroconf在不同平台上支持得很好。你甚至可以配置Safari,让它显示你本地网络上的zeroconf服务,这样你就可以用它来调试消费者的服务广告。这种方法的一个额外好处是,你可以很容易地在不同的机器上运行生产者和消费者。
你需要在本地系统上建立一个客户端-服务器模型。虽然可以使用TCP/IP套接字来让客户端和服务器之间进行通信,但如果不需要通过网络通信,使用本地命名管道会更快。
如果我理解得没错,你的基本需求是这样的:
1. 一个生产者应该能够在没有消费者的情况下创建消费者。
2. 生产者应该能够和消费者进行沟通。
3. 生产者应该能够找到已经存在的消费者并和他们沟通。
4. 即使生产者完成了,消费者也应该继续运行。
5. 多个生产者应该能够和消费者进行沟通。
我们一个一个来解决这些问题:
(1) 这是一个简单的进程创建问题,唯一不同的是,消费者(子进程)即使生产者(父进程)退出后也应该继续运行。见下面的(4)。
(2) 生产者可以通过命名管道与消费者进行沟通。可以参考os.mkfifo()和unix的man页面来创建命名管道。
(3) 当消费者进程启动时,你需要在一个大家都知道的路径下创建命名管道。生产者可以通过查看这个已知的管道来判断是否有消费者在运行。如果管道不存在,就说明没有消费者在运行,生产者可以创建这些消费者。
(4) 你需要使用os.setuid()来实现这一点,并让消费者进程像守护进程一样运行。可以参考unix的man页面了解setsid()。
(5) 这个问题有点棘手。多个生产者可以通过同一个命名管道与消费者进行沟通,但如果你想可靠地识别是哪个生产者发送了数据,或者想防止不同生产者的数据交错,你不能传输超过“PIPE_BUF”大小的数据。
更好的做法是让消费者在执行时打开一个“控制”命名管道(例如/tmp/control.3456,其中3456是消费者的进程ID)。生产者首先通过“控制”管道建立通信通道。当生产者连接时,它会将自己的进程ID,比如“1234”,发送给消费者的“控制”管道,这样消费者就会创建一个用于与生产者交换数据的命名管道,比如“/tmp/data.1234”。然后生产者关闭“控制”管道,打开“/tmp/data.1234”与消费者进行沟通。每个消费者可以有自己的“控制”管道(用消费者的进程ID来区分不同消费者的管道),每个生产者也有自己的“数据”管道。当一个生产者完成时,它应该清理自己的数据管道,或者告诉消费者去清理。同样,当消费者完成时,也应该清理自己的控制管道。
这里的一个难点是防止多个生产者同时连接到同一个消费者的控制管道。“控制”管道是一个共享资源,你需要在不同的生产者之间进行同步来访问它。可以使用信号量或者文件锁定来实现。可以参考posix_ipc这个python模块。
注意:我上面描述的内容大多是基于通用的UNIX语义,但你真正需要的只是能够创建守护进程、能够创建“命名”管道/队列/其他,以便它们可以被不相关的进程找到,以及能够在不相关的进程之间进行同步。你可以使用任何提供这些功能的python模块。
有几种常见的方法可以实现你的第一个目标(检测正在运行的进程),但在使用这些方法之前,你需要稍微调整一下对程序启动背景进程的理解。
想象一下,第一次运行程序时,不是启动了五个进程然后就退出,而是检测到自己是第一个实例,并且不退出。它可以创建一个文件锁(这是防止同一个应用程序多次运行的常见方法之一),或者简单地绑定到某个套接字(另一种常见的方法)。无论哪种方式,第二个实例在尝试启动时都会抛出异常,这样它就知道自己不是第一个实例,可以重新集中精力去联系第一个实例。
如果你使用的是multiprocessing
,你可以简单地使用Manager支持,这涉及到绑定到一个套接字,作为服务器来使用。
第一个程序启动进程,创建队列、代理或其他东西。它创建一个管理器,让其他地方可以访问这些资源,可能还允许远程访问。
后续的运行尝试首先联系这个服务器/管理器,使用预定义的套接字(或者用其他方法找到它所在的套接字)。它们不是调用server_forever()
,而是connect()
并使用通常的multiprocessing
机制进行通信。