【问题标题】:Use python's multiprocessing library in Rust在 Rust 中使用 python 的多处理库
【发布时间】:2020-02-27 16:48:14
【问题描述】:

我使用 Rust 来加速数据处理管道,但我必须按原样运行一些现有的 Python 代码,我想并行化这些代码。在另一个question 中讨论之后,考虑到我的项目的特定限制,创建多个 Python 进程是一种可能的方法。但是,运行下面的代码会产生无限循环。我不太明白为什么。

use cpython::Python;

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();
    py.run(r#"
import sys
from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    print('start')
    sys.argv=['']
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
    "#, None,None).unwrap();
}

输出(一直持续到 Ctrl-C):

start
start
start
start
start
start
start
start

编辑

正如下面的 cmets 所述,我放弃了尝试从 Python 代码创建进程。 Windows、Python 多处理模块以及如何使用 Rust 创建进程之间的干扰太模糊而无法正确管理。 因此,我将从 Rust 创建和管理它们。因此,该代码更具教科书性:

use std::process::Command;
fn main() {
    let mut cmd = Command::new("python");
    cmd.args(&["-c", "print('test')"]); 
    let process = cmd.spawn().expect("Couldn't spawn process.");
    println!("{:?}", process.wait_with_output().unwrap());
}

【问题讨论】:

  • 我无法重现这个。使用最新的 cpython crate (v0.4.1) 和最新的 Python (3.8.1)。
  • 我想 __name__ 在您的情况下始终设置为 "__main__",即使在您的子流程中也是如此。您可以通过将该代码放入 Python 中的 main() 函数中并按照 this example 从 Rust 调用该函数来解决此问题。
  • 你是对的。我在python代码中添加了print(__name__),每个进程打印__main__。如果我在纯 Python 中运行代码,子进程中的__name____mp_main__,它可以防止循环。我检查了您发送的链接,但不明白如何将其应用于我的示例。
  • 把所有__main__ 的东西都抽出来并做一个print(hello) 不会有什么坏处。您也不需要if main - 它可以防止在导入时执行代码,这对您来说不是必需的。一旦打印工作,尝试添加子进程。关键是,尽可能简化python端,然后重新构建它。如果你有问题,你知道你需要修复 rust call config。如果没有,请逐渐添加最少的代码以在 python 上构建您想要的解决方案,
  • 你在windows上,不是吗?

标签: python rust cpython


【解决方案1】:

我无法重现这个;对我来说,它只是按预期打印start,然后是hello bob。无论出于何种原因,在您的情况下,__name__ 似乎总是等于"__main__",并且您会得到这个无限递归。我在 Arch Linux 上使用 cpython crate 版本 v0.4.1 和 Python 3.8.1。

解决方法是完全不依赖__name__,而是将您的Python代码定义为具有main()函数的模块,然后调用该函数:

use cpython::{Python, PyModule};

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let module = PyModule::new(py, "bob").unwrap();
    py.run(r#"
import sys
from multiprocessing import Process

def f(name):
    print('hello', name)

def main():
    print('start')
    sys.argv=['']
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
    "#, Some(&module.dict(py)), None).unwrap();
    module.call(py, "main", cpython::NoArgs, None).unwrap();
}

【讨论】:

  • 我假设你使用 Linux,它使用 fork 作为启动方法。 OP 可能在 Windows 或 MacOS 上,它们使用 spawn 作为启动方法 (docs.python.org/3/library/…)。这是冻结模块和生成的问题,例如参见stackoverflow.com/q/47325297/5769463
  • 是的,没错,我在 Windows 上。正如@thomas 所提到的,我确保我在 cpyton 0.4.1 和 Python 3.8.1 上,但它仍然给出了一个无限循环,消息为 thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: PyErr { ptype: <class '_pickle.PicklingError'>, pvalue: Some(PicklingError("Can't pickle <function f at 0x000001754332C4C0>: import of module 'bob' failed")), ptraceback: Some(<traceback object at 0x000001754447D440>) }', src\libcore\result.rs:1084:5
  • 尝试调用multiprocessing.freeze_support()(但仅在Windows上),如@ead链接到的答案中所述:stackoverflow.com/a/47360452/14637
  • 做到了。 freeze_support 似乎处理了sys.argv 中的内容,但是似乎没有在子进程中设置变量,所以它仍然递归。然后我尝试添加mp.set_executable('c:\\venv\\python.exe'),以便它启动一个python解释器而不是主可执行文件。它不会递归,但会产生另一个错误unknown option --multiprocessing-fork usage: c:\test\target\debug\test.exe [option] ... [-c cmd | -m mod | file | -] [arg] ... Try `python -h' for more information.
  • 嗯。除了冻结之外,您还可以尝试将模块放在磁盘上的单独文件中并调整解释器的路径以便它可以找到它。然后您应该能够使用PyModule::import(py, "bob") 加载模块bob.py,并且不再需要冻结。 (只是猜测,我在这里有点超出我的深度。)
猜你喜欢
  • 2014-05-24
  • 2022-12-04
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-10
相关资源
最近更新 更多