2

我有一个关于 Python 和运行交互式模拟的相当高级别的问题。这是设置:

我正在将我最初在 Smalltalk (VW) 中编写的一些模拟软件移植到 Python。它是一种从图形界面交互控制的循环神经网络。除了控制模拟本身(启动、停止等)之外,该界面还允许实时操作大多数网络参数。在最初的 Smalltalk 实现中,我有两个进程以不同的优先级运行:

  1. 具有更高优先级的接口本身
  2. 神经网络以较低的优先级永远运行

两个进程之间的通信很简单,因为所有 Smalltalk 进程共享相同的地址空间(对象内存)。

我现在开始意识到在 Python 中复制类似的设置并不是那么简单。据我所知,线程模块不允许其线程共享地址空间。多处理模块可以,但方式相当复杂(使用队列等)。

所以我开始认为我的 Smalltalk 观点让我误入歧途,我完全从错误的角度处理了一个相对简单的问题。问题是,我不知道什么是正确的角度!你会建议我如何解决这个问题?我对 Python 相当陌生(显然)并且非常愿意学习。但我非常感谢有关如何构建问题以及我应该深入研究哪些多处理模块(如果有的话!)的建议。

谢谢,

斯特凡诺

4

1 回答 1

0

我将就如何解决这个问题提出我的看法。在multiprocessing模块中PipeQueueIPC 机制确实是最好的方法;尽管您提到了增加的复杂性,但值得学习它们的工作原理。这Pipe是相当简单的,所以我将用它来说明。

这是代码,然后是一些解释:

import sys
import os
import random
import time
import multiprocessing

class computing_task(multiprocessing.Process):
    def __init__(self, name, pipe):
        # call this before anything else
        multiprocessing.Process.__init__(self)

        # then any other initialization
        self.name = name
        self.ipcPipe = pipe
        self.number1 = 0.0
        self.number2 = 0.0
        sys.stdout.write('[%s] created: %f\n' % (self.name, self.number1))

    # Do some kind of computation
    def someComputation(self):
        try:
            count = 0
            while True:
                count += 1
                self.number1 = (random.uniform(0.0, 10.0)) * self.number2
                sys.stdout.write('[%s]\t%d \t%g \t%g\n' % (self.name, count, self.number1, self.number2))

                # Send result via pipe to parent process.
                # Can send lists, whatever - anything picklable.
                self.ipcPipe.send([self.name, self.number1])

                # Get new data from parent process
                newData = self.ipcPipe.recv()
                self.number2 = newData[0]

                time.sleep(0.5)
        except KeyboardInterrupt:
            return

    def run(self):
        sys.stdout.write('[%s] started ...  process id: %s\n' 
                         % (self.name, os.getpid()))        
        self.someComputation()

        # When done, send final update to parent process and close pipe.
        self.ipcPipe.send([self.name, self.number1])
        self.ipcPipe.close()
        sys.stdout.write('[%s] task completed: %f\n' % (self.name, self.number1))

def main():
    # Create pipe
    parent_conn, child_conn = multiprocessing.Pipe()

    # Instantiate an object which contains the computation
    # (give "child process pipe" to the object so it can phone home :) )
    computeTask = computing_task('foo', child_conn)

    # Start process
    computeTask.start()

    # Continually send and receive updates to/from the child process
    try:
        while True:
            # receive data from child process
            result = parent_conn.recv()
            print "recv: ", result

            # send new data to child process
            parent_conn.send([random.uniform(0.0, 1.0)])
    except KeyboardInterrupt:
        computeTask.join()
        parent_conn.close()
        print "joined, exiting"

if (__name__ == "__main__"):
    main()

我已经将要完成的计算封装在一个派生自Process. 在大多数情况下,这并不是绝对必要的,但可以使代码更易于理解和扩展。在主进程中,您可以使用start()此类实例上的方法启动计算任务(这将启动一个单独的进程来运行对象的内容)。

如您所见,我们Pipe在父进程中使用创建两个连接器(管道的“末端”)并将一个给子进程,而父进程持有另一个。这些连接器中的每一个都是持有端的进程之间的双向通信机制,send()以及recv()用于执行其名称所暗示的方法。在本例中,我使用管道来传输数字和文本列表,但通常您可以发送列表、元组、对象或任何可腌制的东西(即可以使用 Python 的腌制工具进行序列化)。因此,您对在进程之间来回发送的内容有一定的自由度。

所以你设置你的连接器,调用start()你的新进程,然后你就可以开始计算了。在这里,我们只是将两个数字相乘,但您可以看到它是在子流程中“交互地”完成的,并从父流程发送了更新。同样,父进程也会定期收到来自计算进程的新结果的通知。

请注意,连接器的recv()方法是阻塞的,即如果另一端还没有发送任何东西,recv()将等待直到有东西可以读取,并在此期间阻止其他任何事情发生。所以请注意这一点。

希望这可以帮助。同样,这是一个准系统示例,在现实生活中,您将希望进行更多错误处理,可能用于poll()连接对象等等,但希望这能传达主要想法并帮助您入门。

于 2013-05-16T15:49:14.013 回答