Execution abruptly halting if the thread / process is killed makes sense
如果线程/进程被杀死,则执行突然停止是有意义的
Why it won't execute cleanup code when I exit the main program normally by clicking the [X] on my terminal window?
当我通过单击终端窗口上的[X]正常退出主程序时,为什么它不会执行清理代码?
I'm still learning the ins-and-outs of multithreaded applications, and I assume my problems come from not understanding how Python handles killing background threads.
我还在学习多线程应用程序的细节,我认为我的问题来自于不了解Python如何处理杀死后台线程。
Questions:
- Why won't my
finally:
block execute all the time? - When else won't a
finally:
block execute? - What happens to code execution inside a thread when the thread is killed?
- What happens to daemon/non-daemon threads when you exit the main process?
为什么我的finally:block一直都不执行?
什么时候不会终于:阻止执行?
当线程被杀死时,线程内的代码执行会发生什么?
退出主进程时守护进程/非守护进程线程会发生什么?
Details:
I'm trying to write a multithreaded program using ZMQ sockets that (among other things) writes stuff to a log file. I want the logging thread to unconditionally perform some messaging and clean-up right before it dies, but it won't most of the time.
我正在尝试使用ZMQ套接字编写多线程程序(其中包括)将东西写入日志文件。我希望日志记录线程在它死之前无条件地执行一些消息传递和清理,但是大部分时间都不会。
The function below starts an infinite loop in a background thread and returns a zmq.PAIR
socket for communication. The loop it starts listens to a socket, and anything written to that socket gets written to the file. The loop also (should) transmit back diagnostic messages like "I'm starting to log now!","Oops, there's been an error!" a "I'm exiting now". so the main program can keep tabs on it.
下面的函数在后台线程中启动一个无限循环,并返回一个zmq.PAIR套接字进行通信。它开始的循环侦听套接字,写入该套接字的任何内容都会写入该文件。循环也(应该)传回诊断消息,例如“我现在开始登录!”,“哎呀,出现了错误!”一个“我现在正在退出”。所以主程序可以密切关注它。
The main
program generates a few threads using this pattern to monitor/control different bits and pieces. It polls several ZMQ sockets (connected to STDIN and a serial port) for messages, and forwards some of them to the socket connected to the file.
主程序使用此模式生成一些线程来监视/控制不同的位和片段。它会轮询几个ZMQ套接字(连接到STDIN和一个串行端口)以获取消息,并将其中一些转发到连接到该文件的套接字。
But now I'm stuck. The main
program's routing & control logic works fine. get_logfile_sock
's file writing works fine, and normal exception handling works as expected. But the "I'm exiting now" code doesn't execute when the thread is killed from the main program, or when I stop the main program altogether.
但现在我被卡住了。主程序的路由和控制逻辑工作正常。 get_logfile_sock的文件编写工作正常,正常的异常处理按预期工作。但是当我从主程序中杀死线程时,或者当我完全停止主程序时,“我现在退出”代码不会执行。
Example:
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
2 个解决方案
#1
2
doesn't execute when the thread is killed
线程被杀死时不执行
Don't kill threads. Ask them nicely to exit and then join
on them. Consider passing in a Condition
for them to check.
不要杀死线程。请他们很好地退出然后加入他们。考虑通过条件让他们检查。
Long answer: executing a kill
will cause the thread to exit without guaranteeing that it complete any particular block and you should not expect good behavior of your system afterwards. It's probably a little safer to do this when using multiprocessing
though.
答案很长:执行kill会导致线程退出而不保证它完成任何特定的块,你不应该期望系统的良好行为。但是,在使用多处理时,这可能会更安全一些。
#2
0
How to enable try: / finally: work as needed
Best practice is to create an own signalling-layer ( which allows many things, incl. sending / receiving a soft SigKILL signal ).
最佳实践是创建一个自己的信令层(允许许多事情,包括发送/接收软SigKILL信号)。
That makes your inter-process messaging architecture "clean" & fully under your control.
这使您的进程间消息传递体系结构“干净”并完全由您控制。
Upon receiving a soft SigKILL, your thread code may handle all the necessary steps, incl. raising your own sub-type of exception(s), that make sense under your intended exception-related structure of:
收到软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 |||||||||||||||||||||||||||||||||||||||||||||||||||||
A demonstrator of FINALLY: clause
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)
How to execute a clean-up code once [x]-window-frame-icon got clicked
For handling a click on the [X]-window-frame-icon, right-top in the window-frame, there is a good solution available in Tkinter. There one can assign this event to be handled by a specialised code ( anEventHANDLER ) that still can survive such killing-kiss and which responsibly executes all dirty things ( incl. taking care to gracefully release all resources ) before the process dies upon being externally terminated by the OS.
为了处理窗口框架右上角的[X] -window-frame-icon的点击,Tkinter提供了一个很好的解决方案。有一个人可以指定这个事件由一个专门的代码(anEventHANDLER)来处理,这个代码仍然可以在这样的kill-kiss中存活下来并负责任地执行所有脏东西(包括注意优雅地释放所有资源),然后在外部终止进程终止之前由操作系统。
Syntax:
win.protocol( 'WM_DELETE_WINDOW', lambda:None ) # blocks this way to terminate
win.protocol( 'WM_DELETE_WINDOW', aSendSigKILL_eventHANDLER )
Having created a soft-signalling between processes allows you to control and dispatch soft-SIGs so as to allow/enforce all distributed threads to get SIG-message and handle their own execution accordingly.
在进程之间创建了一个软信令,允许您控制和分派软SIG,以便允许/强制所有分布式线程获取SIG消息并相应地处理它们自己的执行。
#1
2
doesn't execute when the thread is killed
线程被杀死时不执行
Don't kill threads. Ask them nicely to exit and then join
on them. Consider passing in a Condition
for them to check.
不要杀死线程。请他们很好地退出然后加入他们。考虑通过条件让他们检查。
Long answer: executing a kill
will cause the thread to exit without guaranteeing that it complete any particular block and you should not expect good behavior of your system afterwards. It's probably a little safer to do this when using multiprocessing
though.
答案很长:执行kill会导致线程退出而不保证它完成任何特定的块,你不应该期望系统的良好行为。但是,在使用多处理时,这可能会更安全一些。
#2
0
How to enable try: / finally: work as needed
Best practice is to create an own signalling-layer ( which allows many things, incl. sending / receiving a soft SigKILL signal ).
最佳实践是创建一个自己的信令层(允许许多事情,包括发送/接收软SigKILL信号)。
That makes your inter-process messaging architecture "clean" & fully under your control.
这使您的进程间消息传递体系结构“干净”并完全由您控制。
Upon receiving a soft SigKILL, your thread code may handle all the necessary steps, incl. raising your own sub-type of exception(s), that make sense under your intended exception-related structure of:
收到软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 |||||||||||||||||||||||||||||||||||||||||||||||||||||
A demonstrator of FINALLY: clause
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)
How to execute a clean-up code once [x]-window-frame-icon got clicked
For handling a click on the [X]-window-frame-icon, right-top in the window-frame, there is a good solution available in Tkinter. There one can assign this event to be handled by a specialised code ( anEventHANDLER ) that still can survive such killing-kiss and which responsibly executes all dirty things ( incl. taking care to gracefully release all resources ) before the process dies upon being externally terminated by the OS.
为了处理窗口框架右上角的[X] -window-frame-icon的点击,Tkinter提供了一个很好的解决方案。有一个人可以指定这个事件由一个专门的代码(anEventHANDLER)来处理,这个代码仍然可以在这样的kill-kiss中存活下来并负责任地执行所有脏东西(包括注意优雅地释放所有资源),然后在外部终止进程终止之前由操作系统。
Syntax:
win.protocol( 'WM_DELETE_WINDOW', lambda:None ) # blocks this way to terminate
win.protocol( 'WM_DELETE_WINDOW', aSendSigKILL_eventHANDLER )
Having created a soft-signalling between processes allows you to control and dispatch soft-SIGs so as to allow/enforce all distributed threads to get SIG-message and handle their own execution accordingly.
在进程之间创建了一个软信令,允许您控制和分派软SIG,以便允许/强制所有分布式线程获取SIG消息并相应地处理它们自己的执行。