在 Python 3.9.10 中,我偶然發現了以下非常令人不安的行為:
class MyThread(threading.Thread):
def run(self):
liveness = self.is_alive()
logging.debug(f"Am I alive? {liveness}") # prints FALSE!!!
... # do some work involving asyncio and networking
... # (specifically, I'm using aiohttp) and I know this work is
... # actually being done because I can see its side-effects
... # from across the network.
liveness = self.is_alive()
logging.debug(f"Am I alive? {liveness}") # prints False AGAIN!!!
... # go on with that work (still detectable and detected)
liveness = self.is_alive()
logging.debug(f"Am I alive? {liveness}") # still False...
在某些情況下,該呼叫is_alive()回傳False。現在,我沒有做任何奇怪的事情,比如重新定義MyThread我不應該做的方法,或者搞亂任何東西的內部結構。
我的問題是,在正常情況下,是否存在執行緒啟動后Thread.is_alive會回傳False但仍在作業的情況?(順便說一下,主要是在做 Python 作業,而不是在后臺運行一些 C 代碼。)
更多細節
有一個主執行緒和兩個作業執行緒。它繼續這樣的事情。以下代碼在主執行緒中運行:
exit_signal = threading.Event()
workers = {}
# pass them the exit_signal so they know when to stop:
workers['connect_to_server_1'] = MyThread("server1.com", exit_signal)
workers['connect_to_server_1'].start()
workers['connect_to_server_2'] = MyThread("server2.com", exit_signal)
workers['connect_to_server_2'].start()
# wait until the process gets a SIGINT (user hits ^C)
try:
for w in workers.values():
w.join() # will never return
except KeyboardInterrupt:
logging.info("ok, user wants to quit, let's quit")
else:
logging.critical("threads have quit on their own") # never happens
# list thread statuses
w = workers['connect_to_server_1']
logging.debug(f"Is {w} alive? {w.is_alive()}") # prints FALSE
w = workers['connect_to_server_2']
logging.debug(f"Is {w} alive? {w.is_alive()}") # prints TRUE
# For debugging purposes, give the workers some more time to keep doing
# their jobs. This here is an interesting time window: the main thread
# has already received ^C, but the workers are supposedly not aware of
# that.
time.sleep(10)
# Finally, tell workers to stop, and wait for them to go:
exit_signal.set()
workers['connect_to_server_1'].join()
workers['connect_to_server_2'].join()
logging.info("all good, bye!")
發生的事情是這樣的
Before I hit
^C, I see in my log output messages from both workers telling me that they are heatlhy and successfully doing their jobs; more importantly, those "Am I alive?" messages (from the first code snippet at the top) always sayTrue.After I hit
^C, I see the log messages from the main thread checking theis_alive()status of both workers. I expected it to tell me that both workers are alive, since the interrupt signal always interrupts just the main thread. However, it tells me that the second worker is alive, but the first is not.After that, while the main thread is blocked on that
time.sleep(10)call, I still see messages from both workers in the log output. Both workers tell me that they are successfully doing their jobs (which can be verified by log messages from the other server they're talking to). However, everytime the first worker logs the "Am I alive?" message, it saysFalse. WTF?Finally, I set the
exit_signal.- I see a message from the second worker (the one that was saying
"Am I alive? True"), telling me that it received the signal, and then it goes on doing its shutdown routine, closing files and sockets etc. - I don't see any message from the other worker, and I can't see anywhere anything that indicates that he has received that signal, except for the fact that the
joinmethod on its thread returned successfully!
- I see a message from the second worker (the one that was saying
Closing thoughts
This code has been running in Python 3.6 for months, usually with around 15 workers instead of 2, and this issue never happened. It only happens when I try to run it in Python 3.9. It's somewhat easily reproducible: when I run that service with Python 3.9, around half of the time everything works perfectly, but the in the other half I get scared by this zombie thread telling me that it's dead, yet it's talking to me.
Also, the zombie thread is always the one talking to one specific server, which makes me think that this might be a problem with that one server's SSL certificate, or its implementation of the websocket protocol, but whatever, I don't control that one server. What I do control is the this instance of threading.Thread which should be either dead or walking upright, but not both.
What am I missing here?
uj5u.com熱心網友回復:
原來這是執行緒實作中最近引入的一個錯誤。Thread.join呼叫內部方法Thread._wait_for_tstate_lock,該方法最近更改為如下所示:
try:
if lock.acquire(block, timeout):
lock.release()
self._stop()
except:
if lock.locked():
# bpo-45274: lock.acquire() acquired the lock, but the function
# was interrupted with an exception before reaching the
# lock.release(). It can happen if a signal handler raises an
# exception, like CTRL C which raises KeyboardInterrupt.
lock.release()
self._stop()
raise
如果此方法在和之后立即被 Ctrl-C 中斷,則該if lock.locked()檢查試圖解決發生的掛起問題,但檢查是錯誤的。它不檢查前一個呼叫是否獲得了鎖。它只是檢查鎖是否被鎖定!鎖幾乎總是被鎖定的,特別是,它應該在執行緒處于活動狀態的整個程序中被鎖定。lock.acquirelock.releaselock.acquire
這意味著如果您lock.acquire使用 Ctrl-C 中斷此方法中的呼叫,代碼會釋放鎖(其他人持有的鎖)并呼叫self._stop以執行執行緒結束清理,包括將執行緒標記為不再活動。這就是您的is_alive電話正在回傳的原因False。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/424578.html
