Python asyncio子进程连续写入stdin和读取stdout/stderr

Posted

技术标签:

【中文标题】Python asyncio子进程连续写入stdin和读取stdout/stderr【英文标题】:Python asyncio subprocess write stdin and read stdout/stderr continuously 【发布时间】:2019-08-30 16:03:26 【问题描述】:

我目前正在执行 python3 asyncio 中的子进程任务。我的代码只是写入标准输入并同时读取标准输出/标准错误:

import asyncio


async def read_stdout(stdout):
    print('read_stdout')
    while True:
        buf = await stdout.read(10)
        if not buf:
            break

        print(f'stdout:  buf ')


async def read_stderr(stderr):
    print('read_stderr')
    while True:
        buf = await stderr.read()
        if not buf:
            break

        print(f'stderr:  buf ')


async def write_stdin(stdin):
    print('write_stdin')
    for i in range(100):
        buf = f'line:  i \n'.encode()
        print(f'stdin:  buf ')

        stdin.write(buf)
        await stdin.drain()
        await asyncio.sleep(0.5)


async def run():
    proc = await asyncio.create_subprocess_exec(
        '/usr/bin/tee',
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    await asyncio.gather(
        read_stderr(proc.stderr),
        read_stdout(proc.stdout),
        write_stdin(proc.stdin))


asyncio.run(run())

效果很好,但我在 Python3 document page 上看到了警告:

Warning 使用communicate() 方法而不是process.stdin.write()await process.stdout.read()await process.stderr.read。这避免了由于流暂停读取或写入并阻塞子进程而导致的死锁。

这是否意味着上面的代码在某些情况下会陷入死锁?如果是这样,如何在python3 asyncio中连续写入stdin和读取stdout/stderr而不会出现死锁?

非常感谢。

【问题讨论】:

communicate 等待子进程终止。如果您希望阅读多次(例如阅读某些内容、向标准输入写回复、再次阅读等),那么 communicate 根本无法使用。该警告仅涉及一次性读取的简单情况... 谢谢您的回复,这样反复读/写就没有问题了吗? 【参考方案1】:

警告来自regular 子进程模块,并警告不要尝试实现看起来完全正确的简单通信的幼稚代码,例如:

# write the request to the subprocess
await proc.stdin.write(request)
# read the response
response = await proc.stdout.readline()

如果子进程在读取整个请求之前开始写入响应,这可能会导致死锁。如果响应足够大,子进程将阻塞,等待父进程读取其中的一些并在管道缓冲区中腾出空间。但是,父级不能这样做,因为它仍在写入响应并在开始读取之前等待写入完成。因此,孩子等待父母阅读(部分)其响应,而父母等待孩子完成接受请求。由于双方都在等待对方当前的操作完成,这是一个死锁。

您的代码不存在这个问题,因为您的读取和写入是并行执行的。由于读者从不等待作者,反之亦然,因此没有机会出现(那种)死锁。如果您查看 communicate 和 implemented 的区别,您会发现,除了一些调试日志之外,它的工作方式与您的代码非常相似。

【讨论】:

非常感谢您提供非常详细的回答!

以上是关于Python asyncio子进程连续写入stdin和读取stdout/stderr的主要内容,如果未能解决你的问题,请参考以下文章

使用 asyncio 将 bash 作为 Python 的子进程运行,但 bash 提示被延迟

使用 asyncio 等待子进程的结果

Python - 在单独的子进程或线程中运行 Autobahn|Python asyncio websocket 服务器

在 C++ 中使用 popen 连续写入子进程

python 多进程和多线程3 —— asyncio - 异步IO

python 多进程和多线程3 —— asyncio - 异步IO