从另一个Python脚本调用Python脚本时,Python日志记录挂起

从另一个Python脚本调用Python脚本时,Python日志记录挂起,python,python-2.7,logging,subprocess,deadlock,Python,Python 2.7,Logging,Subprocess,Deadlock,我在python日志类中观察到了这个奇怪的问题,我有两个脚本,一个是从另一个脚本调用的。第一个脚本等待其他脚本结束,而其他脚本使用logging.info 下面是代码片段 #!/usr/bin/env python import subprocess import time import sys chars = ["/","-","\\","|"] i = 0 command = 'sudo python /home/tejto/test/writeIssue.py' process = s

我在python日志类中观察到了这个奇怪的问题,我有两个脚本,一个是从另一个脚本调用的。第一个脚本等待其他脚本结束,而其他脚本使用
logging.info

下面是代码片段

#!/usr/bin/env python

import subprocess
import time
import sys

chars = ["/","-","\\","|"]
i = 0
command = 'sudo python /home/tejto/test/writeIssue.py'
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while process.poll() is None:
    print chars[i],
    sys.stdout.flush()
    time.sleep(.3)
    print "\b\b\b",
    sys.stdout.flush()
    i = (i + 1)%4
output = process.communicate()
另一个脚本是

#!/usr/bin/env python

import os
import logging as log_status

class upgradestatus():
    def __init__(self):
        if (os.path.exists("/tmp/updatestatus.txt")):
                os.remove("/tmp/updatestatus.txt")

        logFormatter = log_status.Formatter("%(asctime)s [%(levelname)-5.5s]  %(message)s")
        logger = log_status.getLogger()
        logger.setLevel(log_status.DEBUG)

        fileHandler = log_status.FileHandler('/tmp/updatestatus.txt', "a")
        fileHandler.setLevel(log_status.DEBUG)
        fileHandler.setFormatter(logFormatter)
        logger.addHandler(fileHandler)

        consoleHandler = log_status.StreamHandler()
        consoleHandler.setLevel(log_status.DEBUG)
        consoleHandler.setFormatter(logFormatter)
        logger.addHandler(consoleHandler)

    def status_change(self, status):
            log_status.info(str(status))

class upgradeThread ():
    def __init__(self, link):
        self.upgradethreadstatus = upgradestatus()
    self.upgradethreadstatus.status_change("Entered upgrade routine")
    procoutput = 'very huge logs, mine were 145091 characters'
    self.upgradethreadstatus.status_change(procoutput)
    self.upgradethreadstatus.status_change("Exiting upgrade routine")

if __name__ == '__main__':
    upgradeclass = upgradeThread(sys.argv[1:]
如果我运行第一个脚本,两个脚本都会挂起,问题似乎在于process.poll()为None时的代码
,如果我对这段代码进行注释,一切都会正常工作。(无法将此与我的问题联系起来!!)

PS我也试着调试python日志类,在其中我发现进程被
StreamHandler
类的emit函数卡住了,它在
stream.write
函数调用中卡住了,并且在写了大量日志后没有出现,但是我的退出日志没有出现

那么,这些脚本中出现死锁情况的问题是什么呢

编辑1(带线程的代码)

script.py 脚本2.py
在这种情况下,t.is_alive函数没有返回false(不知道,但是启动函数已经返回,所以理想情况下它应该返回false!!:()

标准输出缓冲区阻塞

process.communicate()
调用未执行,因为它在
while process.poll()为None:
。因此
writeIssue.py
试图将太多字节写入
stdout
,并且所有字节都在
子流程.PIPE
中得到缓冲,在调用
communicate
之前不会从管道中拉出

缓冲区大小有限。当缓冲区已满时,
stream.write
将被阻止 直到缓冲区有空间。如果缓冲区从未清空(如中发生的情况) 然后进程会死锁

解决方法是在缓冲区完全填满之前调用
communicate()
。您可以通过在线程中启动
writeIssue.py
并在主线程中运行while thread is alive循环时并发调用
communicate()
来实现


script.py

import subprocess
import time
import sys
import threading

def launch():
    command = ['python', 'script2.py']
    process = subprocess.Popen(command)
    process.communicate()

t = threading.Thread(target=launch)
t.start()

chars = ["/","-","\\","|"]
i = 0
while t.is_alive():
    print chars[i],
    sys.stdout.flush()
    time.sleep(.3)
    print "\b\b\b",
    sys.stdout.flush()
    i = (i + 1)%4

t.join()
import sys
import logging
import time

class UpgradeStatus():
    def __init__(self):
        logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s]  %(message)s")
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)

        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.DEBUG)
        consoleHandler.setFormatter(logFormatter)
        self.logger.addHandler(consoleHandler)

    def status_change(self, status):
        self.logger.info(str(status))

class UpgradeThread():
    def __init__(self, link):
        self.upgradethreadstatus = UpgradeStatus()
        self.upgradethreadstatus.status_change("Entered upgrade routine")
        for i in range(5):
            procoutput = 'very huge logs, mine were 145091 characters'
            self.upgradethreadstatus.status_change(procoutput)
            time.sleep(1)
        self.upgradethreadstatus.status_change("Exiting upgrade routine")

if __name__ == '__main__':
    upgradeclass = UpgradeThread(sys.argv[1:])

script2.py

import subprocess
import time
import sys
import threading

def launch():
    command = ['python', 'script2.py']
    process = subprocess.Popen(command)
    process.communicate()

t = threading.Thread(target=launch)
t.start()

chars = ["/","-","\\","|"]
i = 0
while t.is_alive():
    print chars[i],
    sys.stdout.flush()
    time.sleep(.3)
    print "\b\b\b",
    sys.stdout.flush()
    i = (i + 1)%4

t.join()
import sys
import logging
import time

class UpgradeStatus():
    def __init__(self):
        logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s]  %(message)s")
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)

        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.DEBUG)
        consoleHandler.setFormatter(logFormatter)
        self.logger.addHandler(consoleHandler)

    def status_change(self, status):
        self.logger.info(str(status))

class UpgradeThread():
    def __init__(self, link):
        self.upgradethreadstatus = UpgradeStatus()
        self.upgradethreadstatus.status_change("Entered upgrade routine")
        for i in range(5):
            procoutput = 'very huge logs, mine were 145091 characters'
            self.upgradethreadstatus.status_change(procoutput)
            time.sleep(1)
        self.upgradethreadstatus.status_change("Exiting upgrade routine")

if __name__ == '__main__':
    upgradeclass = UpgradeThread(sys.argv[1:])

请注意,如果有两个线程同时写入标准输出,则输出 如果您想避免这种情况,那么所有输出都应该由 带有队列的单线程。所有其他希望 写入输出应将字符串或日志记录推送到 要处理的专用输出线程

然后,该输出线程可以使用for循环从队列中提取输出:

for message from iter(queue.get, None):
    print(message)

标准液缓冲液便秘

process.communicate()
调用未执行,因为它在
while process.poll()为None:
。因此
writeIssue.py
试图将太多字节写入
stdout
,并且所有字节都在
子流程.PIPE
中得到缓冲,在调用
communicate
之前不会从管道中拉出

缓冲区大小有限。当缓冲区已满时,
stream.write
将被阻止 直到缓冲区有空间。如果缓冲区从未清空(如中发生的情况) 然后进程会死锁

解决方法是在缓冲区完全填满之前调用
communicate()
。您可以通过在线程中启动
writeIssue.py
并在主线程中运行while thread is alive循环时并发调用
communicate()
来实现


script.py

import subprocess
import time
import sys
import threading

def launch():
    command = ['python', 'script2.py']
    process = subprocess.Popen(command)
    process.communicate()

t = threading.Thread(target=launch)
t.start()

chars = ["/","-","\\","|"]
i = 0
while t.is_alive():
    print chars[i],
    sys.stdout.flush()
    time.sleep(.3)
    print "\b\b\b",
    sys.stdout.flush()
    i = (i + 1)%4

t.join()
import sys
import logging
import time

class UpgradeStatus():
    def __init__(self):
        logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s]  %(message)s")
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)

        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.DEBUG)
        consoleHandler.setFormatter(logFormatter)
        self.logger.addHandler(consoleHandler)

    def status_change(self, status):
        self.logger.info(str(status))

class UpgradeThread():
    def __init__(self, link):
        self.upgradethreadstatus = UpgradeStatus()
        self.upgradethreadstatus.status_change("Entered upgrade routine")
        for i in range(5):
            procoutput = 'very huge logs, mine were 145091 characters'
            self.upgradethreadstatus.status_change(procoutput)
            time.sleep(1)
        self.upgradethreadstatus.status_change("Exiting upgrade routine")

if __name__ == '__main__':
    upgradeclass = UpgradeThread(sys.argv[1:])

script2.py

import subprocess
import time
import sys
import threading

def launch():
    command = ['python', 'script2.py']
    process = subprocess.Popen(command)
    process.communicate()

t = threading.Thread(target=launch)
t.start()

chars = ["/","-","\\","|"]
i = 0
while t.is_alive():
    print chars[i],
    sys.stdout.flush()
    time.sleep(.3)
    print "\b\b\b",
    sys.stdout.flush()
    i = (i + 1)%4

t.join()
import sys
import logging
import time

class UpgradeStatus():
    def __init__(self):
        logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s]  %(message)s")
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)

        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.DEBUG)
        consoleHandler.setFormatter(logFormatter)
        self.logger.addHandler(consoleHandler)

    def status_change(self, status):
        self.logger.info(str(status))

class UpgradeThread():
    def __init__(self, link):
        self.upgradethreadstatus = UpgradeStatus()
        self.upgradethreadstatus.status_change("Entered upgrade routine")
        for i in range(5):
            procoutput = 'very huge logs, mine were 145091 characters'
            self.upgradethreadstatus.status_change(procoutput)
            time.sleep(1)
        self.upgradethreadstatus.status_change("Exiting upgrade routine")

if __name__ == '__main__':
    upgradeclass = UpgradeThread(sys.argv[1:])

请注意,如果有两个线程同时写入标准输出,则输出 如果您想避免这种情况,那么所有输出都应该由 带有队列的单线程。所有其他希望 写入输出应将字符串或日志记录推送到 要处理的专用输出线程

然后,该输出线程可以使用for循环从队列中提取输出:

for message from iter(queue.get, None):
    print(message)

明白了!这就是我要找的信息…你知道我是否可以清除(清空)已经运行的进程的缓冲区,以便stream.write可以继续运行吗?当使用
stdout=subprocess.PIPE
调用
subprocess.Popen
时,通过调用
process.stdout.read()可以“清除”stdout管道
。直接调用
read()
的问题(例如在主线程的循环中)解决这个问题至少有两种方法:在线程中调用
read
,这就是communicate所做的,也就是上面的代码,或者使用
select。select
轮询stdout和stderr以确定是否有要读取的内容。有一个例子使用后一种方法。@unutbu…我试过你的example但它对我不起作用,,我的代码和你的代码之间的区别是我没有使用“范围(5)中的for i:”,而是在procoutput中使用一个非常大的字符串并记录它…script2.py仍然被卡住…但是在记录最后一个日志“退出升级例程”后,脚本现在被卡住了尽可能避免使用
shell=True
,因为它可能是。如果使用
shell=True
,则
command
应为字符串。如果使用
shell=False
,则
command
应为列表。此外,scipt2.py中存在缩进错误。
shell=True
command=['python','script2.py']
,在Unix上执行的命令是
/bin/sh-c python script2.py
。试试看;你会看到它挂起,因为
script2.py
正在传递给
/bin/sh
,而不是
python
,因此你只需要一个python解释器等待输入。python过程永远不会结束。如果你使用
shell=True
,但要使用
command=“python script2.py”
,则执行的命令是
/bin/sh-c“python脚本”