3

如果线程/进程被杀死,执行突然停止是有意义的

为什么在我的终端窗口点击[X]正常退出主程序时它不会执行清理代码?


我仍在学习多线程应用程序的来龙去脉,我认为我的问题来自于不了解 Python 如何处理杀死后台线程。

问题:

  1. 为什么我的块不会finally:一直执行?
  2. 什么时候不会finally:执行块?
  3. 当线程被杀死时,线程内的代码执行会发生什么?
  4. 当您退出主进程时,守护程序/非守护程序线程会发生什么?

细节:

我正在尝试使用 ZMQ 套接字编写一个多线程程序,该套接字(除其他外)将内容写入日志文件。我希望日志线程在它死之前无条件地执行一些消息传递和清理,但大多数时候不会。

下面的函数在后台线程中启动一个无限循环,并返回一个zmq.PAIR用于通信的套接字。它开始的循环侦听一个套接字,写入该套接字的任何内容都会写入文件。该循环还(应该)发诊断消息,例如“我现在开始登录!”,“糟糕,出现错误!” “我现在要退出”。所以主程序可以密切关注它。

main程序使用此模式生成一些线程来监视/控制不同的位和部分。它轮询几个 ZMQ 套接字(连接到 STDIN 和串行端口)以获取消息,并将其中的一些转发到连接到文件的套接字。

但现在我被困住了。该main程序的路由和控制逻辑工作正常。get_logfile_sock的文件写入工作正常,正常的异常处理按预期工作。但是当线程从主程序中被终止时,或者当我完全停止主程序时,“我现在退出”代码不会执行。

例子:

def get_logfile_sock(context, file_name):
    """
    Returns a ZMQ socket. Anything written to the socket gets appended to the a specified file. The socket will send diagnostic messages about file opening/closing and any exceptions encountered. 

    """

    def log_file_loop(socket):
        """
        Read characters from `socket` and write them to a file. Send back diagnostic and exception information.
        """
        try:
            socket.send("Starting Log File {}".format(file_name))
            with open(file_name, "a+") as fh:
                # File must start with a timestamp of when it was opened
                fh.write('[{}]'.format(get_timestamp()))
                # Write all strings/bytes to the file
                while True:
                    message = socket.recv()

                    fh.write(message)
                    fh.flush()

                    # Un-comment this line to demonstrate that the except: and finally: blocks both get executed when there's an error in the loop
                    # raise SystemExit

        except Exception as e:
            # This works fine when/if there's an exception in the loop
            socket.send("::".join(['FATALERROR', e.__class__.__name__, e.message]))
        finally:
            # This works fine if there's an exception raised in the loop
            # Why doesn't this get executed when my program exits? Isn't that just the main program raising SystemExit? 

            # Additional cleanup code goes here
            socket.send("Closing socket to log file {}".format(file_name))
            socket.close()


    # Make a socket pair for communication with the loop thread
    basename = os.path.basename(file_name).replace(":", "").replace(" ", "_").replace(".", "")
    SOCKNAME = 'inproc://logfile-{}'.format(basename)
    writer = context.socket(zmq.PAIR)
    reader = context.socket(zmq.PAIR)
    writer.bind(SOCKNAME)
    reader.connect(SOCKNAME)

    # Start the loop function in a separate thread
    thread = threading.Thread(target=log_file_loop, args=[writer])
    thread.daemon = True  # is this the right thing to do?
    thread.start()

    # Return a socket endpoint to the thread
    return reader
4

2 回答 2

2

线程被杀死时不执行

不要杀死线程。请他们很好地退出,然后join在他们身上。考虑传入一个Condition让他们检查。

长答案:执行 akill将导致线程退出而不保证它完成任何特定的块,并且您不应该期望之后系统的良好行为。不过,在使用时这样做可能会更安全一些multiprocessing

于 2014-10-01T19:26:05.290 回答
0

如何启用 try: / finally: 按需工作

最佳实践是创建一个自己的信号层(它允许做很多事情,包括发送/接收软 SigKILL 信号)。

这使您的进程间消息传递架构“干净”并完全在您的控制之下。

收到软 SigKILL 后,您的线程代码可能会处理所有必要的步骤,包括。提出您自己的异常子类型,这在您预期的异常相关结构下是有意义的:

try:
   # ... primary flow of a <code-block>-execution
   if ( SigINPUT == "SigKILL" ):
      raise SigKILL_EXCEPTION
except KeyboardInterrupt:
   # ... handle KeyboardInterrupt

except MemoryError:
   # ... handle MemoryError

except NotImplemented:
   # ... handle NotImplemented

except SigKILL_EXCEPTION:
   # ... handle SigKILL_EXCEPTION
   # situation-specific <code-block> shall rather be here, than in "finally:"

   # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
except:
   # ... handle *EXC
finally:
   # +++ ALWAYS DO THIS |||||||||||||||||||||||||||||||||||||||||||||||||||||
   #
   # ... a common <code-block> is ALWAYS executed, under all circumstances
   # ->  put an attempt to RETURN into SigKILL_EXCEPTION section a test this 
   # +++ ALWAYS DO THIS |||||||||||||||||||||||||||||||||||||||||||||||||||||

FINALLY: 子句的演示者

def testTryFinally():
    try:
        print "TRY:"                   # show
        raise KeyboardInterrupt        # used to simulate SigKILL
    except    KeyboardInterrupt:       # EXC. to handle   SigKILL ( emulated by KBDI )
        print  "EXC.KBDI/SigKILL"                # show
        print  "EXC.KBDI:Going to RET(SigKILL)"  # remind the next instr. RET!!
        return "EXC.KBDI:RET(SigKILL)"           # execute RET <value1>
    except:                                      # EXC. collects all unhandled EXC-s
        print  "EXC.*"                           # show
    finally:                                     # FINALLY: clause
        print  "FINALLY: entered"                # show
    return     "RET(End)"                        # execute RET <value2>

>>> testTryFinally()
TRY:
EXC.KBDI/SigKILL
EXC.KBDI:Going to RET
FINALLY: entered
EXC.KBDI:RET(SigKILL)

单击 [x]-window-frame-icon 后如何执行清理代码

为了处理单击窗口框架右上角的 [X]-window-frame-icon,Tkinter 中有一个很好的解决方案。可以将此事件分配给一个专门的代码( anEventHANDLER )处理,该代码仍然可以在这种杀戮之吻中幸存下来,并且在进程因外部终止而死亡之前负责任地执行所有肮脏的事情(包括注意优雅地释放所有资源)由操作系统。

Syntax:
win.protocol( 'WM_DELETE_WINDOW', lambda:None ) # blocks this way to terminate
win.protocol( 'WM_DELETE_WINDOW', aSendSigKILL_eventHANDLER )

在进程之间创建软信号允许您控制和调度软 SIG,以便允许/强制所有分布式线程获取 SIG 消息并相应地处理它们自己的执行。

于 2014-10-02T17:53:05.083 回答