我正在尝试创建一个类,而不是可以运行一个单独的进程去做一些需要很长时间的工作,从主模块中启动一堆这些,然后等待它们全部完成。我想一次启动流程,然后继续为他们提供待办事项,而不是创建和销毁流程。例如,也许我有10个运行dd命令的服务器,然后我希望它们全部scp文件等。
我的最终目标是为每个系统创建一个类,用于跟踪与其绑定的系统的信息,如IP地址,日志,运行时等。但该类必须能够启动系统命令然后在运行该系统命令时将执行返回给调用者,以便稍后使用系统命令的结果进行后续处理。
我的尝试失败了,因为我无法通过pickle将管道上的类的实例方法发送到子进程。那些不是pickleable。因此,我试图以各种方式解决它,但我无法弄明白。如何修补我的代码呢?如果你不能发送任何有用的东西,多处理有什么用?
是否有与类实例一起使用的多处理的良好文档?我可以使多处理模块工作的唯一方法是简单的功能。在类实例中使用它的每次尝试都失败了。也许我应该通过事件呢?我还不明白该怎么做。
import multiprocessing
import sys
import re
class ProcessWorker(multiprocessing.Process):
"""
This class runs as a separate process to execute worker's commands in parallel
Once launched, it remains running, monitoring the task queue, until "None" is sent
"""
def __init__(self, task_q, result_q):
multiprocessing.Process.__init__(self)
self.task_q = task_q
self.result_q = result_q
return
def run(self):
"""
Overloaded function provided by multiprocessing.Process. Called upon start() signal
"""
proc_name = self.name
print '%s: Launched' % (proc_name)
while True:
next_task_list = self.task_q.get()
if next_task is None:
# Poison pill means shutdown
print '%s: Exiting' % (proc_name)
self.task_q.task_done()
break
next_task = next_task_list[0]
print '%s: %s' % (proc_name, next_task)
args = next_task_list[1]
kwargs = next_task_list[2]
answer = next_task(*args, **kwargs)
self.task_q.task_done()
self.result_q.put(answer)
return
# End of ProcessWorker class
class Worker(object):
"""
Launches a child process to run commands from derived classes in separate processes,
which sit and listen for something to do
This base class is called by each derived worker
"""
def __init__(self, config, index=None):
self.config = config
self.index = index
# Launce the ProcessWorker for anything that has an index value
if self.index is not None:
self.task_q = multiprocessing.JoinableQueue()
self.result_q = multiprocessing.Queue()
self.process_worker = ProcessWorker(self.task_q, self.result_q)
self.process_worker.start()
print "Got here"
# Process should be running and listening for functions to execute
return
def enqueue_process(target): # No self, since it is a decorator
"""
Used to place an command target from this class object into the task_q
NOTE: Any function decorated with this must use fetch_results() to get the
target task's result value
"""
def wrapper(self, *args, **kwargs):
self.task_q.put([target, args, kwargs]) # FAIL: target is a class instance method and can't be pickled!
return wrapper
def fetch_results(self):
"""
After all processes have been spawned by multiple modules, this command
is called on each one to retreive the results of the call.
This blocks until the execution of the item in the queue is complete
"""
self.task_q.join() # Wait for it to to finish
return self.result_q.get() # Return the result
@enqueue_process
def run_long_command(self, command):
print "I am running number % as process "%number, self.name
# In here, I will launch a subprocess to run a long-running system command
# p = Popen(command), etc
# p.wait(), etc
return
def close(self):
self.task_q.put(None)
self.task_q.join()
if __name__ == '__main__':
config = ["some value", "something else"]
index = 7
workers = []
for i in range(5):
worker = Worker(config, index)
worker.run_long_command("ls /")
workers.append(worker)
for worker in workers:
worker.fetch_results()
# Do more work... (this would actually be done in a distributor in another class)
for worker in workers:
worker.close()
编辑:我试图移动ProcessWorker
类并在Worker
类之外创建多处理队列,然后尝试手动pickle工作器实例。即使这样也行不通,我收到错误
RuntimeError:只应在进程之间共享队列对象 通过继承
。但我只是将这些队列的引用传递给worker实例?我遗漏了一些基本的东西。以下是主要部分的修改后的代码:
if __name__ == '__main__':
config = ["some value", "something else"]
index = 7
workers = []
for i in range(1):
task_q = multiprocessing.JoinableQueue()
result_q = multiprocessing.Queue()
process_worker = ProcessWorker(task_q, result_q)
worker = Worker(config, index, process_worker, task_q, result_q)
something_to_look_at = pickle.dumps(worker) # FAIL: Doesn't like queues??
process_worker.start()
worker.run_long_command("ls /")
答案 0 :(得分:20)
所以,问题在于我假设Python正在做某种与C ++ / fork()工作方式不同的魔术。我不知何故认为Python只复制了类,而不是将整个程序复制到一个单独的进程中。我严重浪费了几天试图让这个工作,因为所有关于pickle序列化的讨论使我认为它实际上发送了所有东西通过管道。我知道某些事情无法通过管道发送,但我认为我的问题是我没有正确包装。
如果Python文档给了我一个10,000英尺的视图,了解使用此模块时会发生什么,这一切都可以避免。当然,它告诉我多进程模块的方法是做什么的,并给我一些基本的例子,但我想知道的是什么是"操作理论"在幕后!这是我可以使用的那种信息。如果我的回答没有,请发信息。它会帮助我学习。
使用此模块运行启动流程时,整个程序将复制到另一个流程中。但因为它不是" __main__
"进程和我的代码正在检查它,它没有无限启动另一个进程。它只是停下来,坐在那里等待做某事,比如一个僵尸。调用multiprocess.Process()时在父级中初始化的所有内容都已设置完毕并准备就绪。一旦你把东西放在multiprocess.Queue或共享内存,或管道等(但你正在沟通),然后单独的进程接收它并开始工作。它可以在所有导入的模块和设置上绘制,就像它是父项一样。但是,一旦某些内部状态变量在父进程或单独进程中发生更改,则这些更改将被隔离。一旦生成了该过程,现在就可以通过队列,管道,共享内存等方式在必要时使它们保持同步。
我抛弃了代码并重新开始,但现在我只在ProcessWorker
中添加了一个额外的功能,一个"执行"运行命令行的方法。很简单。我不必担心以这种方式启动然后关闭一堆进程,这在过去的C ++中引起了各种不稳定性和性能问题。当我在开始时切换到启动进程然后将消息传递给那些等待进程时,我的性能得到了改善,而且非常稳定。
答案 1 :(得分:8)
尝试发送要执行的方法的名称,而不是尝试发送方法本身(这是不切实际的)。
如果每个工作人员运行相同的代码,那就是一个简单的问题getattr(self, task_name)
。
我会传递元组(task_name, task_args)
,其中task_args
是一个直接用于任务方法的字典:
next_task_name, next_task_args = self.task_q.get()
if next_task_name:
task = getattr(self, next_task_name)
answer = task(**next_task_args)
...
else:
# poison pill, shut down
break
答案 2 :(得分:0)
参考:https://stackoverflow.com/a/14179779
1月6日6:03大卫林奇的答案当他说他被误导时,事实并非正确 http://www.doughellmann.com/PyMOTW/multiprocessing/communication.html
提供的代码和示例是正确的,并按宣传的方式工作。 next_task()
执行通过队列收到的任务 - 尝试了解Task.__call__()
方法正在做什么。
就我而言,绊倒我的是run()
实现中的语法错误。似乎子流程不会报告这个并且只是默默地失败 - 让事情陷入奇怪的循环!确保您运行某种语法检查程序,例如Emacs中的Flymake / Pyflakes。
通过multiprocessing.log_to_stderr()
F进行调试帮助我缩小了问题范围。