我正在使用 perf 探针来分析多线程 Python 应用程序中的 GIL 争用,并且我找到了 take_gil 函数 调用 drop_gil 的序列,如以下捕获的 perf 数据的 perf 脚本转储所示:
viztracer 5533 [003] 3220.317244274: python:take_gil: (55fe99b0d01e)
viztracer 5533 [003] 3220.317407813: python:drop_gil: (55fe99b0cf20)
viztracer 5533 [003] 3220.317412443: python:drop_gil__return: (55fe99b0cf20 <- 55fe99baaa9e)
viztracer 5533 [003] 3220.317419189: python:take_gil: (55fe99b0d01e)
viztracer 5533 [003] 3220.317422951: python:drop_gil: (55fe99b0cf20)
viztracer 5533 [003] 3220.317425869: python:drop_gil__return: (55fe99b0cf20 <- 55fe99baaa9e)
发生这种情况的 CPython 代码中 take_gil 的相应部分如下所示:
if (_PyThreadState_MustExit(tstate)) {
/* bpo-36475: If Py_Finalize() has been called and tstate is not
the thread which called Py_Finalize(), exit immediately the
thread.
This code path can be reached by a daemon thread which was waiting
in take_gil() while the main thread called
wait_for_thread_shutdown() from Py_Finalize(). */
MUTEX_UNLOCK(gil->mutex);
/* tstate could be a dangling pointer, so don't pass it to
drop_gil(). */
drop_gil(interp, NULL, 1);
PyThread_exit_thread();
}
我的问题是,这段代码是在什么条件下执行的?被调用的线程是否即将终止,如
PyThread_exit_thread()
所示。然而,perf 脚本转储表明线程 PID 5533 在之前在时间戳 3220.317419189
删除 GIL 后立即重新尝试在时间戳 3220.317412443
获取 GIL,因此线程 PID 5533 并未终止。
此代码块仅与守护线程有关。如果没有其他活动的非守护线程,守护线程将不会使进程保持活动状态。请参阅https://docs.python.org/3/library/threading.html#threading.Thread.daemon
基本上最后一个非守护线程已经退出,python正在关闭过程中。此时不允许任何线程继续执行Python代码。从此时起,只需进行清理并确保进程不会挂起。请参阅文档上的此注释:
守护进程线程在关闭时突然停止。它们的资源(例如打开的文件、数据库事务等)可能无法正确释放。如果您希望线程正常停止,请将它们设置为非守护进程并使用合适的信号机制,例如事件。
这就是这段代码的含义,进程正在终止,但守护线程已尝试获取 GIL。这被拒绝,并且线程被
PyThread_exit_thread()
立即终止。这不太好,也不干净,但它确实确保了进程退出。