【问题标题】:"subprocess.Popen().readline()" in multithreading python cannot return多线程python中的“subprocess.Popen().readline()”无法返回
【发布时间】:2018-07-05 11:31:23
【问题描述】:

以下代码在Win7下运行,有线程T1和T2,T1在原窗口打印dir内容,T2在新窗口ping 4秒。

import os
import sys
import logging
import subprocess
import threading

class T1 (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        proc = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE)
        for line in iter(proc.stdout.readline, ''):
            logging.debug(line)
        logging.info("HEREEEEEEEE")

class T2 (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        subprocess.Popen(["ping.exe", "-n", "4", "127.0.0.1"], creationflags=subprocess.CREATE_NEW_CONSOLE)
        logging.info("")

if __name__=='__main__':
    logger = logging.getLogger('root')
    FORMAT = "[TID:%(thread)d %(funcName)s L#%(lineno)s] %(message)s"
    logging.basicConfig(format=FORMAT, level=logging.DEBUG)

    t1 = T1()
    t2 = T2()
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    sys.exit(0)

对于T1线程中的logging.info("HEREEEEEEEE")这一行,我认为应该是在打印完dir的内容后立即打印出来的。

什么对我没有意义:为什么该行没有立即打印,而是在线程 T2 完成后 4 秒后打印?

不知道是不是和多线程中的文件描述符有关。

【问题讨论】:

  • @eryksun 它正确打印目录内容,无论如何我更新我的代码以避免混淆。
  • @eryksun 是的,我认为 dir 不是关键。
  • @eryksun 谢谢!这个对我有用。老实说,我不太清楚 close_fds 是如何影响 readline() 的,我会带你去学习更多的技巧。

标签: python windows multithreading subprocess


【解决方案1】:

对于 Python 2,您的代码存在竞争条件,可能会将由 T1 线程创建的管道的可继承写入端泄漏到由 T2 线程创建的 ping.exe 进程中。管道上的readline 在管道关闭之前不会返回,这需要关闭写入端的所有句柄。

在这种情况下,您可以通过在创建 ping.exe 进程时将close_fds=True 传递给Popen 来避免竞争条件。这可以防止它继承可继承的句柄,包括来自T1 线程中重叠调用的管道句柄。

一般来说,在 Python 3.7 之前,如果您需要支持具有覆盖标准句柄的并发 Popen 调用,那么您需要将 Popen 包装为通过预先获取锁来同步调用的函数。不幸的是,这使得Popen 调用成为多线程进程中的瓶颈,但没有简单的替代方案。


背景

在 Unix 中,Popenclose_fds 参数适用于 fork 之后的子进程。如果为真,那么所有非标准文件描述符将在调用exec 之前关闭,即使是那些没有设置FD_CLOEXEC 标志的。但是,它不会关闭标准文件描述符——stdin (0)、stdout (1) 和 stderr (2)。

在 Windows 中,可以将句柄标记为可继承(即HANDLE_FLAG_INHERIT)。默认情况下,句柄是不可继承的。如果在 bInheritHandles 为 true 的情况下调用 CreateProcess,则所有可继承的句柄都由子进程继承。 Popen 将此作为 not close_fds 传递,即不关闭文件描述符意味着继承句柄 [*]。

在Windows中,Popenstdinstdoutstderr参数用于在STARTUPINFO中显式设置子句柄(即hStdInputhStdOutputhStdError) )。如果标准句柄没有被显式覆盖,则父级的标准句柄会被控制台应用程序(例如 python.exe)隐式继承,而不是 GUI 应用程序(例如 pythonw.exe)。如果显式设置,句柄必须是可继承的,并且bInheritHandles(即not close_fds)必须为真。当另一个线程对同样继承句柄的CreateProcess 进行重叠调用时,这就是竞争条件的来源。

在 Python 3 中,当标准句柄未被覆盖时,通过默认 close_fds 为 true 来降低这种竞争条件的频率。在 3.7 中,通过在 STARTUPINFOEXlpAttributeList 字段中传递标准句柄进一步缓解了这种情况。通过此更改,对Popen 的并发调用可以覆盖标准句柄而不会泄漏句柄。但是,句柄仍然必须是可继承的,因此对于其他继承句柄的函数的并发调用仍然存在竞争条件,例如os.systemos.spawnl


[*] 请注意,尽管有参数名称,但 C 的“文件描述符”实际上并未在 Windows 中继承。虽然 C 运行时可以继承 systemspawn / exec 函数的文件描述符,但它使用 STARTUPINFO 来实现这一点是未记录的。因此Popen 只继承句柄。继承非标准句柄时,您需要将句柄值传递给子进程(例如,通过标准输入、命令行或环境变量),您可以通过msvcrt.get_osfhandle 获取。孩子可以通过msvcrt.open_osfhandle为继承的句柄打开一个新的文件描述符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-17
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    相关资源
    最近更新 更多