Python的主线程在停止时会收集垃圾吗?

时间:2013-06-04 23:07:33

标签: python python-2.6 python-multithreading

在一个多线程的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)>]

因为有人总是询问用例......

我的网络服务偶尔会错过其实时截止日期(在最坏的情况下会导致整个系统出现故障)。结果是因为只要文件系统发脾气,就会阻止(重要的)DEBUG数据的记录。所以我试图改进一些已建立的专用日志库来推迟阻塞工作线程的I / O.

可悲的是,已建立的使用模式是记录重叠并行事务的短期日志记录通道和永远不会明确关闭的长期模块范围通道的混合。

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

除了一个重要的用例外,这似乎工作正常:当日志记录通道位于主线程中时。当主线程停止/退出日志记录通道时,已完成,因此我的(非守护程序)工作线程继续使整个进程保持活动状态。

1 个答案:

答案 0 :(得分:3)

如果不在所有非守护程序线程上调用join,或者对未执行的操作做出任何假设,那么主线程结束是个坏主意。


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

你的主线程根本没有退出;它在_MainThread._exitfunc试图join某个任意非守护程序线程内阻塞。在调用atexit处理程序之前,它的对象不会被终结,直到它完成加入所有非守护程序线程之后才会发生。


同时,如果你避免这种情况(例如,直接使用thread / _thread,或者将主线程从其对象中分离出来或强制它进入正常的Thread实例),怎么了?它没有定义。 threading模块根本没有引用它,但是在CPython 2.0-3.3中,并且可能在任何其他合理的实现中,它由thread/_thread module来决定。并且,正如文档所说:

  

当主线程退出时,系统定义其他线程是否存活。在使用本机线程实现的SGI IRIX上,它们存活下来。在大多数其他系统上,它们被杀死而不执行try ... finally子句或执行对象析构函数。

因此,如果您设法避免join所有非守护程序线程,则必须编写可以处理像守护程序线程一样硬死的代码,并让它们继续运行直到退出。

如果它们继续运行,至少在POSIX系统上的CPython 2.7和3.3中,主线程的OS级别线程句柄以及代表它的各种高级Python对象可能仍然保留,并且不会被清除由GC。


最重要的是,即使所有内容都已发布,您也无法依赖GC删除任何内容。如果您的代码依赖于确定性GC,那么很多情况下您可以在CPython中使用它(尽管您的代码将在PyPy,Jython,IronPython等中断开),但在退出时不是其中之一。 CPython可以并且将会在退出时泄漏对象并让操作系统排除。 (这就是为什么你永远不会关闭的可写文件可能会丢失最后几次写入 - __del__方法永远不会被调用,因此没有人告诉他们flush,至少在POSIX上是基础{ {1}}也不会自动刷新。)

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


最后一件事:

  

我原本预计线程本地会被解除分配(即垃圾收集)......

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