Python的主线程在停止时是否得到垃圾回收?

2024-10-04 03:24:43 发布

您现在位置:Python中文网/ 问答频道 /正文

在多线程Python进程中,我有许多非守护进程线程,我指的是那些在主线程退出/停止之后仍保持主进程活动的线程。在

我的非守护进程线程对主线程中的某些对象持有weak references,但是当主线程结束(控制从文件底部脱落)时,这些对象看起来是垃圾回收的,我的弱引用终结器回调不会触发。在

我错误地期望主线程被垃圾回收吗?我本以为线程局部变量会被释放(即垃圾回收)。。。在

我错过了什么?在


支持材料

来自pprint.pprint( threading.enumerate() )的输出,显示主线程已停止,而其他线程仍在运行。在

[<_MainThread(MainThread, stopped 139664516818688)>,
 <LDQServer(testLogIOWorkerThread, started 139664479889152)>,
 <_Timer(Thread-18, started 139663928870656)>,
 <LDQServer(debugLogIOWorkerThread, started 139664437925632)>,
 <_Timer(Thread-17, started 139664463103744)>,
 <_Timer(Thread-19, started 139663937263360)>,
 <LDQServer(testLogIOWorkerThread, started 139664471496448)>,
 <LDQServer(debugLogIOWorkerThread, started 139664446318336)>]

因为有人总是问起用例。。。在

我的网络服务偶尔会错过它的实时截止日期(这在最坏的情况下会导致整个系统故障)。这原来是因为每当文件系统发脾气时,(重要的)调试数据的日志记录都会被阻塞。因此,我试图改造一些已建立的专门的日志库,将阻塞I/O的时间延迟到工作线程。在

遗憾的是,已建立的使用模式是记录重叠并行事务的短期日志记录通道和从未显式关闭的长期模块范围通道的混合。在

所以我创建了一个decorator,它将方法调用延迟到工作线程。工作线程是非守护进程,以确保在解释器退出之前完成所有(慢)阻塞I/O,并保存对客户端的弱引用(方法调用在这里排队)。当客户端被垃圾回收时,弱引用的回调将触发,工作线程知道不会有更多的工作进入队列,因此将在下次方便时退出。在

这似乎在除一个重要用例外的所有情况下都能正常工作:当日志记录通道在主线程中时。当主线程停止/退出时,日志记录通道是而不是完成的,因此我的(非守护进程)工作线程将继续保持整个进程的活动状态。在


Tags: 对象进程记录情况线程thread主线垃圾
1条回答
网友
1楼 · 发布于 2024-10-04 03:24:43

如果主线程在所有非守护进程线程上不调用join,或者对不调用的情况做出任何假设,这是个坏主意


如果您没有做任何非常不寻常的事情,CPython(至少是2.0-3.3)将自动调用所有非守护进程线程上的join,作为_MainThread._exitfunc对。这实际上并没有被记录下来,所以你不应该依赖它,但它是发生在你身上的事情。在

您的主线程实际上根本没有退出;它在其_MainThread._exitfunc中阻塞了试图join的任意非守护进程线程。在调用atexit处理程序之前,它的对象不会最终完成,直到它完成连接所有非守护进程线程之后才会发生。在


同时,如果您避免这种情况(例如,通过直接使用thread/_thread,或者通过将主线程从其对象中分离出来,或者强制它进入一个正常的Thread实例),会发生什么?它没有被定义。threading模块根本没有引用它,但是在cpython2.0-3.3中,以及在任何其他合理的实现中,它由^{}/^{} module来决定。而且,正如医生所说:

When the main thread exits, it is system defined whether the other threads survive. On SGI IRIX using the native thread implementation, they survive. On most other systems, they are killed without executing try ... finally clauses or executing object destructors.

因此,如果您设法避免join所有非守护进程线程,那么您必须编写既能像守护进程线程一样硬终止它们,又能让它们继续运行直到退出的代码。在

如果它们继续运行,至少在POSIX系统上的cpython2.7和3.3中,主线程的OS级线程句柄和代表它的各种高级Python对象可能仍然保留,而不会被GC清理。在


最重要的是,即使所有内容都已发布,也不能指望GC删除任何内容。如果您的代码依赖于确定性的GC,那么在CPython中有很多情况可以避免这种情况(尽管您的代码将在PyPy、Jython、IronPython等中崩溃),但是在退出时不是其中之一。CPython可以,也将在退出时泄漏对象,并让操作系统进行分类。(这就是为什么从不关闭的可写文件可能会在最后几次写入中丢失__del__方法永远不会被调用,因此没有人告诉它们flush,而且至少在POSIX上,底层的FILE*也不会自动刷新。)

如果您想在主线程完成时清理某些内容,则必须使用某种close函数,而不是依赖__del__,并且必须确保它是通过代码主块周围的with块、atexit函数或其他机制触发的。在


最后一件事:

I would have expected that the thread-locals would be deallocated (i.e. garbage collected)...

你真的有线程本地人吗?或者你只是指只能在一个线程中访问的局部变量和/或全局变量?在

相关问题 更多 >