空的python进程在join [sys.stderr.flush()]上挂起

时间:2017-05-19 12:08:24

标签: python multithreading logging multiprocessing fork

Python大师我需要你的帮助。我遇到了很奇怪的行为: 空python进程挂起。看起来它会分配一些锁定的资源。

ENV:

  • Python版本:3.5.3
  • OS:Ubuntu 16.04.2 LTS
  • 内核:4.4.0-75-generic

问题描述:

1)我有一个带有线程的记录器来处理该线程的后台和队列中的消息。 Logger source code(稍微简化一点)。

2)我有一个简单的脚本,它使用我的记录器(只是代码来显示我的问题):

import os
from multiprocessing import Process
from my_logging import get_logger


def func():
    pass


if __name__ == '__main__':

    logger = get_logger(__name__)
    logger.start()
    for _ in range(2):
        logger.info('message')

    proc = Process(target=func)
    proc.start()
    proc.join(timeout=3)
    print('TEST PROCESS JOINED: is_alive={0}'.format(proc.is_alive()))

    logger.stop()
    print('EXIT')

有时候这个测试脚本会挂起。脚本在加入进程“proc”时挂起(当脚本完成执行时)。测试过程“proc”保持活力。

要重现此问题,您可以循环运行脚本:

$ for i in {1..100} ; do /opt/python3.5.3/bin/python3.5 test.py ; done

调查:

Strace显示以下内容:

strace: Process 25273 attached
futex(0x2275550, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL, ffffffff

我想出了进程挂起的地方。它挂在多处理模块,文件process.py,第269行(python3.5.3),在刷新STDERR:

...
267    util.info('process exiting with exitcode %d' % exitcode)
268    sys.stdout.flush()
269    sys.stderr.flush()
...

如果第269行注释,脚本总是成功完成。

我的想法:

默认情况下,logging.StreamHandler使用sys.stderr作为流。

如果在记录器将数据刷新到STDERR时已分叉进程,则进程上下文会获得一些锁定的资源,并在刷新STDERR时进一步挂起。

解决问题的一些解决方法:

  • 使用python2.7。我无法用python2.7重现它。也许时间阻止我重现这个问题。
  • 使用进程处理记录器中的消息而不是线程。

您对此行为有什么想法吗?问题出在哪儿?我做错了吗?

2 个答案:

答案 0 :(得分:1)

看起来此行为与此问题有关:http://bugs.python.org/issue6721

答案 1 :(得分:0)

  

问题:有时......测试过程“proc”保持活力。

     

我只能重现你的

     
TEST PROCESS:0 JOINED: is_alive=True
         

time.sleep(5)添加def func():     您使用proc.join(timeout=3),这是预期的行为。

  
     

<强>结论
  超载您的系统,在我的环境中启动 30个正在运行的进程,触发您的proc.join(timeout=3)。   您可以重新考虑 Testcase 以重现您的问题。

     

我认为,一种方法是使用Process/Threadtime.sleep(0.05)进行微调以发布时间片

  1. 您正在使用from multiprocessing import Queue 请改用from queue import Queue

      
        

    来自文档
        类多处理.Queue
        用于多处理(而非多线程)上下文的队列类。

      
  2. class QueueHandler(logging.Handler):中,阻止

    self.queue.put_nowait(record)
    
         

    class QueueListener(object):
    ...
    def stop(self):
        ...
    
         

    实现,例如

    class QueueHandler(logging.Handler):
      def __init__(self):
          self.stop = Event()
          ...
    
  3. def _monitor(self):中仅使用 ONE while ...循环。
    等到self._thread停止

    class QueueListener(object):
    ...
    def stop(self):
         self.handler.stop.set()
         while not self.queue.empty():
             time.sleep(0.5)
         # Don't use double flags
         #self._stop.set()
         self.queue.put_nowait(self._sentinel)
         self._thread.join()