Python3循环引用还是会自动垃圾回收的。

实验揭示Python如何处理循环引用的垃圾回收,发现约每200对循环引用变量会自动清理一次,进程内存监控显示内存大小基本稳定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前都听说Python循环引用会导致引用数无法清零,所以不能垃圾回收,会内存泄漏,需要删除引用关系或者用gc.collect()才能进行正常垃圾回收。

但做了下实验,好像还是会自动回收循环引用的变量,在约44对循环引用变量时会首次清理,之后大约每产生200对的垃圾就会清一次,但也没清干净,总是会有残留上几个,而且听说了改__del__会影响垃圾回收,实质上也不会影响。

进程内存监控,可以看出内存大小基本没变,还是符合认知的。

====运行环境====

win10 1903
Python 3.7.5 64bit

=====代码=====

import psutil

class A:
    t = 0
    def __del__(self):
        A.t += 1

class B:
    t = 0
    def __del__(self):
        B.t += 1

if __name__ == '__main__':
    pid = os.getpid()
    at, bt = 0, 0
    for i in range(10000):
        a, b = A(), B()
        a.b, b.a = b, a
        #del a, b
        if A.t != at or B.t != bt:
        print(f'No_{i}: (d_i: {i - at}, d_a: {A.t - at}, d_b: {B.t - bt})')
        print('Used Memory:', psutil.Process(pid).memory_info().rss / 1024 / 1024, 'MB')
        at, bt = A.t, B.t

====打印结果====

No_44: (d_i: 44, d_a: 43, d_b: 43)
Used Memory: 13.69921875 MB
No_236: (d_i: 193, d_a: 190, d_b: 190)
Used Memory: 13.8046875 MB
No_431: (d_i: 198, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_626: (d_i: 200, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_822: (d_i: 202, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_1017: (d_i: 203, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_1212: (d_i: 204, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_1407: (d_i: 206, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_1603: (d_i: 208, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_1798: (d_i: 209, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_1993: (d_i: 210, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_2188: (d_i: 212, d_a: 212, d_b: 212)
Used Memory: 13.8046875 MB
No_2384: (d_i: 196, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_2579: (d_i: 197, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_2774: (d_i: 198, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_2969: (d_i: 200, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_3165: (d_i: 202, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_3360: (d_i: 203, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_3555: (d_i: 204, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_3750: (d_i: 206, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_3946: (d_i: 208, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_4141: (d_i: 209, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_4336: (d_i: 210, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_4531: (d_i: 212, d_a: 211, d_b: 211)
Used Memory: 13.8046875 MB
No_4727: (d_i: 197, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_4922: (d_i: 198, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_5117: (d_i: 199, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_5312: (d_i: 201, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_5508: (d_i: 203, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_5703: (d_i: 204, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_5898: (d_i: 205, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_6093: (d_i: 207, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_6289: (d_i: 209, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_6484: (d_i: 210, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_6679: (d_i: 211, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_6874: (d_i: 213, d_a: 211, d_b: 211)
Used Memory: 13.8046875 MB
No_7070: (d_i: 198, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_7265: (d_i: 199, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_7460: (d_i: 200, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_7655: (d_i: 202, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_7851: (d_i: 204, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_8046: (d_i: 205, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_8241: (d_i: 206, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_8436: (d_i: 208, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_8632: (d_i: 210, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_8827: (d_i: 211, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_9022: (d_i: 212, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_9217: (d_i: 214, d_a: 211, d_b: 211)
Used Memory: 13.8046875 MB
No_9413: (d_i: 199, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_9608: (d_i: 200, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB
No_9803: (d_i: 201, d_a: 193, d_b: 193)
Used Memory: 13.8046875 MB
No_9998: (d_i: 203, d_a: 194, d_b: 194)
Used Memory: 13.8046875 MB

====实验结束====

之前了解Python3的print是线程安全的,但在这个垃圾回收的实验里,如果在__del__函数里添加print,在自动回收垃圾时有一定概率(概率小于20%)报出非阻塞的错误。
不过似乎只在Power shell会报错,在cmd里似乎不会报错,但考虑到cmd的输出远快于Power shell,可能是达不到并发标准所以在cmd才不报错。
而且是在此期间所有同时被回收的对象都会同时报错,还不太了解什么原因,不知道是不是并发回收对象的问题。

报错信息如下,一般一出就是4百条同时来:

RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>

 

================================

其实原因还是很清楚的,就是不可重入,和线程安全是两回事。

<think>嗯,用户现在遇到了一个RuntimeError,错误信息是“cannot call asyncSlot without a running loop”。我需要先理解这个错误的原因,然后给出解决方案。 首先,用户可能是在使用qasync库的asyncSlot装饰器时遇到了这个问题。根据之前的对话,用户已经处理过TypeError,现在这个RuntimeError可能和事件循环有关。错误信息明确指出没有正在运行的事件循环,所以问题可能出在Qt应用没有正确集成asyncio的事件循环。 我应该回顾一下qasync的用法。记得qasync需要将Qt的事件循环和asyncio的结合起来,通常是通过QEventLoop或者使用qasync.run()来启动应用。如果用户没有正确设置事件循环,就会导致asyncSlot无法找到正在运行的循环,从而抛出这个错误。 接下来,我需要考虑用户可能的错误场景。比如,用户可能直接使用了app.exec()而没有用qasync.run(),或者在创建应用实例后没有正确设置事件循环。还有可能在异步任务启动之前,事件循环还没有运行。 然后,我应该提供具体的解决方案。包括如何正确初始化事件循环,使用QEventLoop或者qasync.run(),并给出代码示例。此外,还需要检查用户是否在非主线程中调用了asyncSlot,或者过早地调用了异步方法。 还需要提醒用户常见的注意事项,比如确保所有异步操作都在事件循环启动后执行,避免在构造函数中直接调用异步方法,以及正确处理库的版本兼容性问题。最后,给出一个完整的代码示例,帮助用户对照检查自己的代码结构。</think>**错误原因分析**: 当使用`@asyncSlot`装饰的异步槽函数时抛出`RuntimeError: cannot call asyncSlot without a running loop`错误,核心原因是未正确设置Qt事件循环与asyncio的集成。这通常发生在以下场景中: 1. 未使用qasync的专用事件循环启动应用 2. 在主窗口初始化前未配置事件循环 3. 尝试在事件循环启动前执行异步操作 **完整解决方案**: **1. 基础配置方法**(推荐): ```python import sys import asyncio from PySide6 import QtWidgets from qasync import QEventLoop, asyncSlot # ✅ 正确初始化方式 app = QtWidgets.QApplication(sys.argv) loop = QEventLoop(app) asyncio.set_event_loop(loop) # 关键设置 class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setup_ui() def setup_ui(self): btn = QtWidgets.QPushButton("Start Async Task", self) btn.clicked.connect(self.on_click) # 正确绑定信号 @asyncSlot async def on_click(self): print("开始执行耗时操作...") await asyncio.sleep(2) print("操作完成!") if __name__ == "__main__": window = MainWindow() window.show() with loop: loop.run_forever() # 正确启动事件循环 ``` **2. 使用快捷启动方式**(qasync >= 0.23.0): ```python import qasync def main(): app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() qasync.run(app.exec()) # 自动集成事件循环 if __name__ == "__main__": main() ``` **错误场景排查表**: | 错误场景 | 现象 | 修复方法 | |----------------------------|---------------------------|--------------------------| | 直接使用`app.exec()` | 报错且异步失效 | 改用`qasync.run(app.exec())` | | 在构造函数中调用异步方法 | 窗口未显示前触发异步操作 | 使用`QTimer.singleShot(0, init_async)`延迟初始化 | | 多窗口应用中循环被覆盖 | 子窗口操作导致主循环停止 | 确保使用单一全局事件循环 | | 第三方库与qasync冲突 | 出现不可预知的循环崩溃 | 检查库是否兼容asyncio,使用`nest_asyncio`修补 | **高级配置技巧**: ```python # 强制验证事件循环状态 def async_safe_call(fn): async def wrapper(*args, **kwargs): if not asyncio.get_event_loop().is_running(): raise RuntimeError("事件循环未运行") return await fn(*args, **kwargs) return wrapper class SafeWorker: @asyncSlot @async_safe_call async def critical_operation(self): await asyncio.sleep(1) print("安全执行关键操作") ``` **常见问题解答**: 1. **为什么在Jupyter中报错?** - Jupyter自带事件循环,需使用特殊配置: ```python from qasync import QApplication app = QApplication.instance() or QApplication([]) ``` 2. **如何调试事件循环状态?** ```python print("循环运行状态:", asyncio.get_event_loop().is_running()) print("当前任务数:", len(asyncio.all_tasks())) ``` 3. **多线程场景如何处理?** ```python @asyncSlot async def cross_thread_call(self): # 确保切换到主线程执行 await asyncio.get_running_loop().run_in_executor(None, sync_function) ``` **典型错误对照**: ```python # ❌ 危险写法:直接启动异步任务 class WrongWindow(QMainWindow): def __init__(self): self.start_async() # 此时循环尚未启动 async def start_async(self): await asyncio.sleep(1) # 触发RuntimeError # ✅ 正确写法:延迟初始化 class CorrectWindow(QMainWindow): def __init__(self): QtCore.QTimer.singleShot(0, self.delayed_init) def delayed_init(self): asyncio.create_task(self.start_async()) ``` 通过以上配置,可以确保Qt应用与asyncio事件循环正确集成,从根本上避免`RuntimeError`的发生。实际开发中建议使用`qasync.run()`快捷方式,可自动处理90%以上的事件循环集成问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值