我有一个嵌入到 C++ MPI 应用程序中的 Python 3 解释器。此应用程序加载脚本并将其传递给解释器。
当我在没有 MPI 启动器的情况下在 1 个进程上执行程序时(只需调用 ./myprogram),脚本会正确执行并且它的“打印”语句输出到终端。当脚本有错误时,我使用 PyErr_Print() 在 C++ 端打印它。
但是,当我通过 mpirun 启动程序时(即使在单个进程上),我没有从 python 代码中的“打印”中得到任何输出。当我的脚本有错误时,我也没有从 PyErr_Print() 得到任何信息。
我猜想 Python 处理标准输出的方式与 MPI(这里实际上是 Mpich)处理将进程的输出重定向到启动器并最终重定向到终端的方式不匹配。
关于如何解决这个问题的任何想法?
[编辑,遵循本期的建议]
您需要flush_io()在每次调用后PyErr_Print,flush_io该函数可能在哪里:
void flush_io(void)
{
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback); // in Python/pythonrun.c, they save the traceback, let's do the the same
for (auto& s: {"stdout", "stderr"}) {
PyObject *f = PySys_GetObject(s);
if (f) PyObject_CallMethod(f, "flush", NULL);
else PyErr_Clear();
}
PyErr_Restore(type, value, traceback);
}
Run Code Online (Sandbox Code Playgroud)
[在我的旧分析下面,它仍然有一些有趣的信息]
我最终遇到了同样的问题(PyErr_Print无法从 mpirun 工作)。追溯(涉及 python3 的一些 gdb)并比较工作的东西(./myprogram)和非工作的东西(mpirun -np 1 ./myprogram),我最终得到了_io_TextIOWrapper_write_impl(./Modules/_io/textio.c:1277 顺便说一句,python-3.6.0)。
两次运行之间的唯一区别是self->line_buffering1 与 0(此时self代表sys.stderr)。然后,在 中pylifecycle.c:1128,我们可以看到是谁决定了这个值:
if (isatty || Py_UnbufferedStdioFlag)
line_buffering = Py_True;
Run Code Online (Sandbox Code Playgroud)
所以看来 MPI 在启动程序之前对 stderr 做了一些操作,这使得它不是一个 tty。我还没有调查 mpirun 中是否有一个选项可以在 stderr 上保留 tty 标志...如果有人知道,那会很有趣(尽管转念一想 mpi 可能有充分的理由将他的文件描述符放在 stdout&stderr 的位置,例如其 --output-filename)。
有了这些信息,我可以提出 3 个解决方案(前 2 个是快速修复,第三个更好):
1/ 在启动 python 解释器的 C 代码中,在创建 sys.stderr 之前设置缓冲标志。代码变为:
Py_UnbufferedStdioFlag = 1; // force line_buffering for _all_ I/O
Py_Initialize();
Run Code Online (Sandbox Code Playgroud)
这使得 Python 的回溯在所有情况下都回到屏幕上;但可能会产生灾难性的 I/O...因此只有在调试模式下才是可接受的解决方案。
2/在python(嵌入式)脚本中,在最开始添加以下内容:
import sys
#sys.stderr.line_buffering = True # would be nice, but readonly attribute !
sys.stderr = open("error.log", 'w', buffering=1 )
Run Code Online (Sandbox Code Playgroud)
然后,该脚本将回溯转储到该文件 error.log。
我还尝试在 PyErr_Print() 之后添加对 fflush(stderr) 或 fflush(NULL) 的调用...但这不起作用(因为 sys.stderr 有自己的内部缓冲)。不过,这将是一个很好的解决方案。
3/经过一番挖掘,我发现了完美的功能
Python/pythonrun.c:57:static void flush_io(void);
Run Code Online (Sandbox Code Playgroud)
事实上,它是在该文件中的每个 PyErr_Print 之后调用的。不幸的是它是静态的(仅存在于该文件中,Python.h 中没有引用它,至少在 3.6.0 中)。我将该函数从该文件复制到我的程序中,结果证明它完全可以完成这项工作。