为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

潇湘沐 2021-03-22 14:10:38
在多线程Python进程中,我有许多非守护进程线程,这是指即使在主线程退出/停止后仍使主进程保持活动状态的线程。我非守护线程持有弱引用在主线程对某些对象,但是当主线程结束(控制脱落的文件的底部),这些对象就不会出现被垃圾收集,我的弱引用finaliser回调别开火。我期望主线程被垃圾回收是错误的吗?我本来希望将线程局部变量释放(即收集垃圾)...我错过了什么?配套材料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推迟到工作线程中。可悲的是,已建立的使用模式是混合记录并行事务的短期日志记录通道和从未明确关闭的长期模块作用域通道的混合。因此,我创建了一个装饰器,该装饰器将对方法的调用推迟到工作线程中。worker线程是非守护程序,以确保所有(缓慢的)阻塞I / O在解释器退出之前完成,并保持对客户端的弱引用(方法调用入队)。当客户端进行垃圾回收时,弱引用的回调将触发,并且工作线程知道不再有更多的工作排队,因此将在下一个方便时退出。在一个重要的用例中,这似乎可以正常工作:在日志记录通道位于主线程中时。当主线程停止/退出时,日志记录通道尚未完成,因此,我的(非守护程序)工作线程始终保持整个进程运行。
查看完整描述

1 回答

?
慕妹3242003

TA贡献1824条经验 获得超6个赞

对于您的主线程而言,如果不调用join所有非守护程序线程而结束,或者对不执行该操作会做出任何假设,则是一个不好的主意。


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

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


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

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

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

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


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

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


最后一件事:

我本来希望将线程局部变量释放(即收集垃圾)...

您实际上在某处有线程局部变量吗?或者,您是说仅在一个线程中访问的局部变量和/或全局变量?


查看完整回答
反对 回复 2021-03-30
  • 1 回答
  • 0 关注
  • 208 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号