python - 如果作为 Python 子进程调用,多线程 Perl 脚本会导致管道损坏

标签 python multithreading perl subprocess

我正在使用子进程从 Python 3.7.3 调用 Perl 脚本。调用的 Perl 脚本是这样的:

https://github.com/moses-smt/mosesdecoder/blob/master/scripts/tokenizer/tokenizer.perl

我用来调用它的代码是:

import sys
import os
import subprocess
import threading

def copy_out(source, dest):
    for line in source:
        dest.write(line)

num_threads=4

args = ["perl", "tokenizer.perl",
        "-l", "en",
        "-threads", str(num_threads)
       ]

with open(os.devnull, "wb") as devnull:
    tokenizer = subprocess.Popen(args,
        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=devnull)

tokenizer_thread = threading.Thread(target=copy_out, args=(tokenizer.stdout, open("outfile", "wb")))
tokenizer_thread.start()

num_lines = 100000

for _ in range(num_lines):
    tokenizer.stdin.write(b'Random line.\n')

tokenizer.stdin.close()
tokenizer_thread.join()

tokenizer.wait()

在我的系统上,这会导致以下错误:

Traceback (most recent call last):
  File "t.py", line 27, in <module>
    tokenizer.stdin.write(b'Random line.\n')
BrokenPipeError: [Errno 32] Broken pipe

我对此进行了调查,结果发现,如果子进程的 -threads 参数为 1,则不会引发错误。由于我不想放弃子进程中的多线程,所以我的问题是:

首先是什么导致了这个错误? “谁”应该为此负责:操作系统/环境、我的 Python 代码、Perl 代码?

如果需要,我很高兴提供更多信息。


编辑:要回复一些评论,

  • 只有当您还有此文件时才能运行 Perl 脚本:https://github.com/moses-smt/mosesdecoder/blob/master/scripts/share/nonbreaking_prefixes/nonbreaking_prefix.en
  • Perl 脚本在进程失败之前实际上处理了数千行。在上面的 Python 脚本中,如果我将 num_lines 变小,我就不会再收到此错误。
  • 如果我只是在命令行上调用此 Perl 脚本,而不使用任何 Python,它就可以正常工作:无论有多少(Perl)线程或输入行。
  • 我的Python变量num_threads仅控制Perl子进程的线程数。我从不启动多个 Python 线程,只启动一个。

编辑 2:在我的第一次编辑中,我错误地指出这个 Perl 程序在使用例如调用时运行良好。来自命令行的 -threads 4:其中使用了使用多线程编译的不同 Perl。如果我使用从 Python 调用的相同 Perl,我会得到:

$ cat [file with 100000 lines] | [correct perl] tokenizer.perl -l en -threads 4
Can't locate object method "new" via package "Thread" at
tokenizer.perl line 130, <STDIN> line 8000.

这无疑会帮助我更好地调试它。

最佳答案

问题似乎是如果 perl 不支持线程,perl 脚本就会崩溃。您可以通过运行以下命令来检查您的 perl 是否支持线程:

perl -MConfig -E 'say "Threads supported" if $Config{useithreads}'

就我而言,输出为空,因此我安装了一个具有线程支持的新 Perl:

perlbrew install perl-5.30.0 --as=5.30.0-threads -Dusethreads
perlbrew use 5.30.0-threads

然后我再次运行Python脚本:

import sys
import os
import subprocess
import threading

def copy_out(source, dest):
    for line in iter(source.readline, b''):
        dest.write(line)

num_threads=4
args = ["perl", "tokenizer.perl",
        "-l", "en",
        "-threads", str(num_threads)
       ]
tokenizer = subprocess.Popen(
    args,
    bufsize=-1,  #use default bufsize = 8192 bytes
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.DEVNULL)

tokenizer_thread = threading.Thread(
    target=copy_out, args=(tokenizer.stdout, open("outfile", "wb")))
tokenizer_thread.start()

num_lines = 100000

for _ in range(num_lines):
    tokenizer.stdin.write(b'Random line.\n')

tokenizer.stdin.close()
tokenizer_thread.join()
tokenizer.wait()

现在它运行到最后,没有错误,并生成了包含 100000 行的输出文件 outfile

关于python - 如果作为 Python 子进程调用,多线程 Perl 脚本会导致管道损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61343709/

相关文章:

python - 检查 Django Rest Framework API LIST/DATABASE 中是否存在记录

c++ - "data ready"标志的同步机制?

c# - 免注册类 COM 互操作和线程

perl - 从 CPAN dist 文件中获取 "defined"模块?

perl - Perl源代码中的 "1;"是什么?

python - genfromtxt() 中的 NumPy dtype 问题,将字符串读取为 bytestring

python - wx.Validator 没有被调用

html - 使用 Perl 解析 html 适用于 2 行但不是多行

python - Python 的 fork 连接模型实现? (相当于Java的ForkJoinPool)

c# - 无法将元组添加到 BlockingCollection : Error CS1503 in C#