如何在Python中控制模拟

时间:2013-04-15 16:29:58

标签: python multithreading multiprocessing simulation

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

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

  1. 具有更高优先级的界面本身
  2. 神经网络永远以较低的优先级运行
  3. 两个进程之间的通信很简单,因为所有Smalltalk进程共享相同的地址空间(对象内存)。

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

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

    谢谢,

    斯特凡诺

1 个答案:

答案 0 :(得分:0)

我会就如何解决这个问题提出看法。在multiprocessing模块中,PipeQueue IPC机制确实是最好的方法;尽管你提到了更多的复杂性,但值得了解它们的工作原理。 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的pickle工具序列化)。所以你对进程之间来回发送的东西有一定的自由度。

因此,您需要设置连接器,在新进程上调用start(),然后关闭并进行计算。这里我们只是将两个数字相乘,但您可以看到它在子流程中以“交互方式”完成,并从父项发送更新。同样,定期向父流程通知计算过程的新结果。

请注意,连接器的recv()方法是阻塞的,即如果另一端尚未发送任何内容,recv()将等待某些内容被读取,并防止其他任何内容发生与此同时。所以请注意这一点。

希望这会有所帮助。同样,这是一个准系统的例子,在现实生活中你会想要做更多的错误处理,可能在连接对象上使用poll(),等等,但希望这能传达出主要的想法并让你开始。 / p>