如果操作 stdin 和 stdout,Python subprocess.popen 会死锁?

问题描述 投票:0回答:1

我编写了一个脚本来操作国际象棋引擎的标准输入和标准输出。基本上,我使用 subprocess.popen 打开程序,并使用 threading.Thread 操作每个输入和输出。 但是,如果生成了我的脚本的多个实例,程序将在大约 15 分钟后停止工作。我认为这是由于文档描述的缓冲区已满而导致的死锁。不幸的是,我无法使用推荐的“通信”功能,因为我需要不断修改输入和输出。有什么办法可以处理吗?这是我所做的最小版本:

import sys
import time
import subprocess
import threading


class ChessEngine:

    def __init__(self):
        self.lock = threading.Lock()

    def write(self, command):
        with self.lock:
            self.engine.stdin.write(command + "\n")
            self.engine.stdin.flush()

    def read_stdout(self):
        while True:
            text = self.engine.stdout.readline()
            text = text.replace("foo", "bar")
            print(text, end="", flush=True)

    def read_stdin(self):
        while True:
            command = input()
            command = command.replace("foo", "bar")
            self.write(command)

    def run(self):
        self.engine = subprocess.Popen(
            "/usr/games/stockfish",
            shell=True,
            universal_newlines=True,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            bufsize=1,
            cwd=self.folder,
        )
        threading.Thread(target=self.read_stdout, daemon=True).start()
        threading.Thread(target=self.read_stdin, daemon=True).start()
        while True:
            time.sleep(1)
            if not self.engine.poll() is None:
                sys.exit(0)


if __name__ == "__main__":
    chess_engine = ChessEngine()
    chess_engine.run()

我寻找了几种方法来确保脚本不会阻塞 - 但没有成功。

python subprocess popen chess
1个回答
0
投票

似乎真正的问题是 stderr 缓冲区已满,因为您没有读取它。 所以,如果你不读stderr,你可以丢弃它:

self.engine = subprocess.Popen(
    "/usr/games/stockfish",
    shell=True,
    universal_newlines=True,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.DEVNULL,  # DISCARD IT!
    bufsize=1,
    cwd=self.folder,
)

在这种情况下,缓冲区不会填满。

您还可以将 stderr 重定向到 stdout,就像您在

read_stdin
中阅读的那样:

self.engine = subprocess.Popen(
    "/usr/games/stockfish",
    shell=True,
    universal_newlines=True,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,  # REDIRECT IT
    bufsize=1,
    cwd=self.folder,
)

这个解决方案也可以工作。

最后你可以开始在另一个线程中阅读你的stderr:

def read_stderr(self):
    while True:
        text = self.engine.stderr.readline()
        ...
        pass

def run(self):
    self.engine = subprocess.Popen(
        "/usr/games/stockfish",
        shell=True,
        universal_newlines=True,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        bufsize=1,
        cwd=self.folder,
    )
    threading.Thread(target=self.read_stdout, daemon=True).start()
    threading.Thread(target=self.read_stdin, daemon=True).start()
    threading.Thread(target=self.read_stderr, daemon=True).start()  # START REEAD THIS
    ...
© www.soinside.com 2019 - 2024. All rights reserved.