在基于Trio的Python应用程序中生成进程并在进程之间进行通信

时间:2018-07-04 10:11:11

标签: python python-3.x numpy multiprocessing python-trio

对于在Python库fluidimage上的实习,我们正在研究使用库trio用客户机/服务器模型编写HPC并行应用程序是否是一个好主意。

对于异步编程和I / O,三重奏确实很棒!

然后,我想知道如何

  1. 生成进程(服务器完成CPU-GPU的工作)
  2. 在进程之间通信复杂的Python对象(可能包含大的numpy数组)。

在文档中,即使使用the echo client/server tutorial,我也没有找到推荐的方法来用trio做到这一点。

在Python中产生进程并进行通信的一种明显方法是使用multiprocessing

在HPC环境中,我认为一个好的解决方案是使用MPI(http://mpi4py.readthedocs.io/en/stable/overview.html#dynamic-process-management)。作为参考,我还必须提到rpychttps://rpyc.readthedocs.io/en/latest/docs/zerodeploy.html#zerodeploy)。

我不知道是否可以将这些工具与三重奏一起使用,什么是正确的方法。

一个有趣的相关问题

备注PEP 574

在我看来PEP 574(请参阅https://pypi.org/project/pickle5/)也可能是解决此问题的好方法的一部分。

5 个答案:

答案 0 :(得分:4)

不幸的是,截至今天(2018年7月),Trio尚不支持子进程或任何用于MPI或其他高级进程间协调协议的高级包装程序的生成和通信。

这绝对是我们最终要达到的目标,如果您想更详细地讨论需要实施的内容,则可以hop in our chatthis issue进行概述核心子流程支持需要什么。但是,如果您的目标是在几个月内完成一些实习工作,说实话,您可能想考虑使用更成熟的HPC工具,例如dask

答案 1 :(得分:2)

截至2018年中,Trio尚未这样做。迄今为止,您最好的选择是使用trio_asyncio来利用asyncio对Trio仍然需要学习的功能的支持。

答案 2 :(得分:0)

我在主程序和服务器中发布了一个使用多处理和三重奏的非常幼稚的代码示例。似乎可行。

from multiprocessing import Process, Queue
import trio
import numpy as np

async def sleep():
    print("enter sleep")
    await trio.sleep(0.2)
    print("end sleep")

def cpu_bounded_task(input_data):
    result = input_data.copy()
    for i in range(1000000-1):
        result += input_data
    return result

def server(q_c2s, q_s2c):
    async def main_server():
        # get the data to be processed
        input_data = await trio.run_sync_in_worker_thread(q_c2s.get)
        print("in server: input_data received", input_data)
        # a CPU-bounded task
        result = cpu_bounded_task(input_data)
        print("in server: sending back the answer", result)
        await trio.run_sync_in_worker_thread(q_s2c.put, result)

    trio.run(main_server)

async def client(q_c2s, q_s2c):
    input_data = np.arange(10)
    print("in client: sending the input_data", input_data)
    await trio.run_sync_in_worker_thread(q_c2s.put, input_data)
    result = await trio.run_sync_in_worker_thread(q_s2c.get)
    print("in client: result received", result)

async def parent(q_c2s, q_s2c):
    async with trio.open_nursery() as nursery:
        nursery.start_soon(sleep)
        nursery.start_soon(client, q_c2s, q_s2c)
        nursery.start_soon(sleep)

def main():
    q_c2s = Queue()
    q_s2c = Queue()
    p = Process(target=server, args=(q_c2s, q_s2c))
    p.start()
    trio.run(parent, q_c2s, q_s2c)
    p.join()

if __name__ == '__main__':
    main()

答案 3 :(得分:0)

一个简单的带有mpi4py的示例...从三重奏的角度来看,这可能是一个不好的工作,但似乎可行。

通信是通过trio.run_sync_in_worker_thread完成的,因此(as written by Nathaniel J. Smith)(1)没有取消(也没有对Control-C的支持)和(2)比三重任务使用的内存更多(但是一个Python线程没有)使用那么多的内存)。

但是对于涉及大型numpy数组的通信,自communication of buffer-like objects is going to be very efficient with mpi4py开始,我会这样。

import sys
from functools import partial

import trio

import numpy as np
from mpi4py import MPI

async def sleep():
    print("enter sleep")
    await trio.sleep(0.2)
    print("end sleep")

def cpu_bounded_task(input_data):
    print("cpu_bounded_task starting")
    result = input_data.copy()
    for i in range(1000000-1):
        result += input_data
    print("cpu_bounded_task finished ")
    return result

if "server" not in sys.argv:
    comm = MPI.COMM_WORLD.Spawn(sys.executable,
                                args=['trio_spawn_comm_mpi.py', 'server'])

    async def client():
        input_data = np.arange(4)
        print("in client: sending the input_data", input_data)
        send = partial(comm.send, dest=0, tag=0)
        await trio.run_sync_in_worker_thread(send, input_data)

        print("in client: recv")
        recv = partial(comm.recv, tag=1)
        result = await trio.run_sync_in_worker_thread(recv)
        print("in client: result received", result)

    async def parent():
        async with trio.open_nursery() as nursery:
            nursery.start_soon(sleep)
            nursery.start_soon(client)
            nursery.start_soon(sleep)

    trio.run(parent)

    print("in client, end")
    comm.barrier()

else:
    comm = MPI.Comm.Get_parent()

    async def main_server():
        # get the data to be processed
        recv = partial(comm.recv, tag=0)
        input_data = await trio.run_sync_in_worker_thread(recv)
        print("in server: input_data received", input_data)
        # a CPU-bounded task
        result = cpu_bounded_task(input_data)
        print("in server: sending back the answer", result)
        send = partial(comm.send, dest=0, tag=1)
        await trio.run_sync_in_worker_thread(send, result)

    trio.run(main_server)
    comm.barrier()

答案 4 :(得分:0)

您还可以查看 tractor,它似乎终于发布了第一个 Alpha 版本。

它具有使用 TCP 和 trio(但我认为他们计划了更多传输)的内置函数式 RPC 系统(很像 msgpack)。您只需直接调用其他进程中的函数,然后以各种不同的方式将结果传回/获取。

这是他们的第一个例子:

"""
Run with a process monitor from a terminal using::

    $TERM -e watch -n 0.1  "pstree -a $$" \
        & python examples/parallelism/single_func.py \
        && kill $!

"""
import os

import tractor
import trio


async def burn_cpu():

    pid = os.getpid()

    # burn a core @ ~ 50kHz
    for _ in range(50000):
        await trio.sleep(1/50000/50)

    return os.getpid()


async def main():

    async with tractor.open_nursery() as n:

        portal = await n.run_in_actor(burn_cpu)

        #  burn rubber in the parent too
        await burn_cpu()

        # wait on result from target function
        pid = await portal.result()

    # end of nursery block
    print(f"Collected subproc {pid}")


if __name__ == '__main__':
    trio.run(main)