两个独立的Python引擎之间的通信

4
问题陈述如下:
我正在使用用于分析机械问题的程序Abaqus。它基本上是一个独立的Python解释器,具有自己的对象等。在这个程序中,我运行一个Python脚本来设置我的分析(因此可以修改此脚本)。它还包含必须在接收到外部信号时执行的方法。这些信号来自我自己的Python引擎中运行的主脚本。
目前,我的工作流程如下: 当Abaqus脚本必须执行特定函数时,主脚本将布尔值设置为True,并将其放入文件中。Abaqus脚本定期检查此文件,以查看布尔值是否已设置为true。如果是,则进行分析并pickle输出,以便主脚本可以读取此输出并对其进行操作。
我正在寻找一种更有效的方法来指示另一个进程启动分析,因为现在存在很多不必要的检查。通过pickle进行数据交换对我来说不是问题,但更有效的解决方案肯定受欢迎。
搜索结果总是给出类似subprocess之类的解决方案,这适用于在同一解释器内启动的两个进程。我还看过ZeroMQ,因为它应该实现这样的功能,但我认为这太复杂了,希望能有Python的解决方案。两个解释器都运行Python 2.7(尽管版本不同)。

1
两个进程的要求是什么?它们需要分开吗?它们必须在不同的机器上运行吗?是否需要使用独立的用户ID?您为什么选择了两个进程来实现您的方法,并且有哪些限制? - aghast
最好让“主”进程成为一个Python 3.x环境,并调用例如"abaqus python aq_script.py",该脚本读取并将abaqus数据进行封装。然后再使用另一个输出脚本返回到abaqus,以相同的方式进行封装。虽然速度较慢,但如果需要scipytensorflow等库,它仍然比在abaqus的古老numpy实现中实现要快。 - Daniel F
@AustinHastings:两个进程需要分开,但是它们都在同一台机器上运行。我的“主脚本”必须在主Python引擎上运行,因为它不需要在每种情况下都启动Abaqus。这样,Abaqus只是程序的一部分,只有在需要时才会被访问。 - mooisjken
@agentp:abaqus是在我的主脚本中作为子进程调用的:subprocess.call('abaqus cae script = test.py',shell=True)。这会输出一条消息,表示abaqus许可证服务器已启动,并且打开了给定脚本的abaqus程序。我需要一种方法从我的主脚本与该脚本进行通信。像之前提到的那样反过来构建并不合逻辑。 - mooisjken
@DanielForsman:避免使用abaqus古老的numpy确实是同时运行两个独立程序的好处之一。我想知道你的解决方案是否与我的当前解决方法相符?还有,我该如何让Abaqus在接收信号时更加高效,而不是不断地检查一个已经被pickled的文件是否发生了变化? - mooisjken
显示剩余4条评论
3个回答

5

编辑:

像@MattP一样,我会添加我的理解声明:

背景

我认为你正在运行一个名为abaqus的产品。abaqus产品包括一个链接的python解释器,你可以以某种方式访问它(可能是通过在命令行上运行abaqus python foo.py)。

您还有一个单独的python安装,在同一台机器上。您正在开发代码,可能包括numpy / scipy,以在该python安装中运行。

这两个安装是不同的:它们具有不同的二进制解释器,不同的库,不同的安装路径等。但是它们位于同一物理主机上。

您的目标是使由您编写的“纯Python”程序与在“Abaqus Python”环境中运行的一个或多个脚本进行通信,以便这些脚本可以在Abaqus系统内执行工作并返回结果。

解决方案

这里是基于套接字的解决方案。有两个部分:abqlistener.pyabqclient.py。这种方法的优点是使用了一个明确定义的“等待工作”的机制。没有文件轮询等。而且它是一个“硬”API。您可以从同一台机器上运行相同版本的python的进程,或者从不同的机器上连接到侦听器进程,或者从不同版本的python,ruby或C或perl甚至COBOL中连接到它。它允许您在系统中放置真正的“气隙”,因此您可以以最小的耦合开发两个部分。

服务器部分是abqlistener。意图是您将会把这些代码复制到您的Abaqus脚本中。然后,abq进程将成为一个服务器,在特定端口号上监听连接,并响应工作。发送回复,或不发送。等等。

我不确定您是否需要为每个作业做设置工作。如果是这样,那将成为连接的一部分。这只是启动ABQ,在一个端口上监听(永远),并处理请求。任何特定于作业的设置都必须是工作过程的一部分。 (也许发送参数字符串或配置文件的名称或其他内容。)

客户端部分是abqclient。这可以移到模块中,也可以只是复制/粘贴到您现有的非ABQ程序代码中。基本上,您打开与正确的主机:端口组合的连接,然后您正在与服务器交谈。发送一些数据,获取一些数据,等等。

这些东西主要是从在线示例代码中抓取的。因此,如果您开始挖掘任何内容,它应该看起来很熟悉。

这里是abqlistener.py:

# The below usage example is completely bogus. I don't have abaqus, so
# I'm just running python2.7 abqlistener.py [options]
usage = """
abacus python abqlistener.py [--host 127.0.0.1 | --host mypc.example.com ] \\
        [ --port 2525 ]

Sets up a socket listener on the host interface specified (default: all
interfaces), on the given port number (default: 2525). When a connection
is made to the socket, begins processing data.
"""



import argparse

parser = argparse.ArgumentParser(description='Abacus listener',
    add_help=True,
    usage=usage)

parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
                    help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
                    help='port number of listener (default: 2525)')

args = parser.parse_args()

import SocketServer
import json

class AbqRequestHandler(SocketServer.BaseRequestHandler):
    """Request handler for our socket server.

    This class is instantiated whenever a new connection is made, and
    must override `handle(self)` in order to handle communicating with
    the client.
    """

    def do_work(self, data):
        "Do some work here. Call abaqus, whatever."
        print "DO_WORK: Doing work with data!"
        print data
        return { 'desc': 'low-precision natural constants','pi': 3, 'e': 3 }

    def handle(self):
        # Allow the client to send a 1kb message (file path?)
        self.data = self.request.recv(1024).strip()
        print "SERVER: {} wrote:".format(self.client_address[0])
        print self.data
        result = self.do_work(self.data)
        self.response = json.dumps(result)
        print "SERVER: response to {}:".format(self.client_address[0])
        print self.response
        self.request.sendall(self.response)


if __name__ == '__main__':
    print args
    server = SocketServer.TCPServer((args.host, args.port), AbqRequestHandler)
    print "Server starting. Press Ctrl+C to interrupt..."
    server.serve_forever()

以下是abqclient.py的代码:

usage = """
python2.7 abqclient.py [--host HOST] [--port PORT]

Connect to abqlistener on HOST:PORT, send a message, wait for reply.
"""

import argparse

parser = argparse.ArgumentParser(description='Abacus listener',
    add_help=True,
    usage=usage)

parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
                    help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
                    help='port number of listener (default: 2525)')

args = parser.parse_args()

import json
import socket

message = "I get all the best code from stackoverflow!"

print "CLIENT: Creating socket..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print "CLIENT: Connecting to {}:{}.".format(args.host, args.port)
s.connect((args.host, args.port))

print "CLIENT: Sending message:", message
s.send(message)

print "CLIENT: Waiting for reply..."
data = s.recv(1024)

print "CLIENT: Got response:"
print json.loads(data)

print "CLIENT: Closing socket..."
s.close()

当我将它们一起运行时,下面是它们打印的内容:

$ python2.7 abqlistener.py --port 3434 &
[2] 44088
$ Namespace(host='', port=3434)
Server starting. Press Ctrl+C to interrupt...

$ python2.7 abqclient.py --port 3434
CLIENT: Creating socket...
CLIENT: Connecting to :3434.
CLIENT: Sending message: I get all the best code from stackoverflow!
CLIENT: Waiting for reply...
SERVER: 127.0.0.1 wrote:
I get all the best code from stackoverflow!
DO_WORK: Doing work with data!
I get all the best code from stackoverflow!
SERVER: response to 127.0.0.1:
{"pi": 3, "e": 3, "desc": "low-precision natural constants"}
CLIENT: Got response:
{u'pi': 3, u'e': 3, u'desc': u'low-precision natural constants'}
CLIENT: Closing socket...

参考资料:

argparseSocketServerjsonsocket 都是“标准” Python 库。


尚未完全测试,但至少可以说在abaqus pythonimport语句是有效的,这意味着它应该可以工作。由于我不是很擅长编程,当通过套接字传递大数据(1GB +)时,是否有任何陷阱需要注意?通常我想传递一个大字典或numpy数组,这样做是否可行,还是应该将其作为文本流发送? - Daniel F
对于这么多的数据,你需要将它分成较小的块进行发送 - 比如4kb或16kb等。你还应该考虑将文件写出并传递文件名 - 我不知道哪种方式会获得更好的性能。 - aghast
所以仍然将数据存储到磁盘中进行序列化和反序列化,但至少主脚本和abaqus脚本有一种方法可以告诉彼此它们的部分何时完成。 - Daniel F
针对可能的未来Abaqus用户: 这是我目前的工作流程: 我在我的主脚本中使用 subprocess.Popen('abaqus cae script=C:\FCP\workspace\TestAbaqus.py', shell=True)启动abaqus。由于启动abaqus需要一些时间(大约10秒),我们无法立即在主脚本中建立连接。因此,我保持尝试在while-try-except结构中建立连接,直到成功为止。之后,由于我知道此时abaqus环境已经完全运行,因此可以运行主脚本的其余部分。 - mooisjken
你是否在客户端每次都“关闭”连接?这可能是为什么你不能进行交互的原因... - aghast
显示剩余2条评论

0

我同意这个答案,除了一些小的语法问题。

在处理程序内定义实例变量是不可取的。更不要说它们没有被定义在任何类型的init()方法中。子类化TCPServer并在TCPServer.init()中定义您的实例变量。其余部分将保持不变。


0

明确一下,我的理解是您正在通过Python脚本作为独立进程(我们称之为abq.py)运行Abaqus/CAE,该脚本检查、打开和读取触发文件以确定是否应该运行分析。触发文件由第二个Python进程(我们称之为main.py)创建。最后,main.py等待读取由abq.py创建的输出文件。您希望有一种更有效的方法来向abq.py发出运行分析的信号,并且您可以使用不同的技术来交换数据。

正如您提到的,子进程或多进程可能是一个选项。但是,我认为更简单的解决方案是将您的两个脚本合并,并可选择使用回调函数来监视解决方案并处理输出。我假设没有必要将abq.py作为单独的进程持续运行,并且只要适当时就可以从main.py启动所有分析。

main.py可以访问Abaqus Mdb。如果已经构建好了,可以使用以下方式打开:

mdb = openMdb(FileName)

如果main.py启动所有分析,则不需要触发文件。例如:

if SomeCondition:
    j = mdb.Job(name=MyJobName, model=MyModelName)
    j.submit()
    j.waitForCompletion()

完成后,main.py 可以读取输出文件并继续执行。如果数据文件是由分析本身生成的(例如 .dat.odb 文件),那么这很简单。但是,如果输出文件是由当前 abq.py 中的某些代码生成的,则可以将其包含在 main.py 中。

如果这样还不够控制,您可以向 monitorManager 对象添加回调函数,而不是使用 waitForCompletion 方法(当您导入 abaqus 模块时会自动创建该对象:from abaqus import *)。这允许您监视并响应求解器发送的各种消息,例如 COMPLETEDITERATION 等。回调函数的定义如下:

def onMessage(jobName, messageType, data, userData):
    if messageType == COMPLETED:
        # do stuff
    else:
        # other stuff

然后将其添加到monitorManager中,调用作业:

monitorManager.addMessageCallback(jobName=MyJobName,  
    messageType=ANY_MESSAGE_TYPE, callback=onMessage, userData=MyDataObj)
j = mdb.Job(name=MyJobName, model=MyModelName)
j.submit()

这种方法的好处之一是,您可以将Python对象作为userData参数传递。这可能是您的输出文件或其他数据容器。您可能可以在回调函数中找出如何处理输出数据 - 例如,访问Odb并获取数据,然后根据需要进行任何操作,而无需使用外部文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接