V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lithbitren
V2EX  ›  Python

在做 Python 循环引用垃圾回收实验中的一个小问题, Python3 的 print 是线程安全的吗?

  •  
  •   lithbitren · 2020-03-13 17:32:41 +08:00 · 3154 次点击
    这是一个创建于 1751 天前的主题,其中的信息可能已经有所发展或是发生改变。
    以前都听说 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>'>
    11 条回复    2020-03-14 00:25:01 +08:00
    limyel
        1
    limyel  
       2020-03-13 17:43:59 +08:00
    我记得循环引用不是用标记清除来解决吗
    lithbitren
        2
    lithbitren  
    OP
       2020-03-13 17:45:59 +08:00
    @limyel 是的,一直都是这么听说的,如果循环引用数量少的话(少于 44 对时),在__del__里发打印,会发现程序退出的时候才会打印。
    lithbitren
        3
    lithbitren  
    OP
       2020-03-13 17:49:49 +08:00
    顺便问问怎么在输入框里正常贴代码,直接贴好像缩进会被铲掉。
    ipwx
        4
    ipwx  
       2020-03-13 17:51:02 +08:00
    Python 因为有 ref-counter,所以标记清扫很懒惰的吧。也是因为更推荐能用 weakref 就用 weakref
    ppyybb
        5
    ppyybb  
       2020-03-13 19:39:53 +08:00 via iPhone
    错误挺明显了,print 是个不可重入的函数,你在 del 里面写,会导致 print 可能被调用了一半后中断切到另外一个 del 里面又执行 print,导致报错。详情可以去知乎搜灵剑老大关于 gc 导致的不安全问题的文章。这个和线程安全不一样,是在一个线程里面切换的。如果 print 线程不安全,那么平常你 call print 也没有加锁啊……
    lithbitren
        6
    lithbitren  
    OP
       2020-03-13 22:55:09 +08:00
    @ppyybb 谢谢大佬提示,学 c 的时候学过不过那时还不了解并行,把函数的重入性和线程安全搞混了,后来玩 python 几乎不会出线程相关错误,所以是第一次见这个错误,错误信息直接丢网上大多都在说线程安全问题,所以以为就是线程安全问题,现在搜了搜资料大概是了解。

    灵剑大大的那篇文章也没具体说什么情况会报错,感觉这应该算是一个例子吧。

    不过灵剑大大在评论区里提到了 pypy,pypy 很多扩展和函数都不能直接用,不过测了下好像也是会自动 GC 的,不过启动阈值很大, 而且并不是成对清理的,开 n = 1 000 000 才发现__del__函数的调用。psutil 不能用,任务管理器里进程内存的变化肉眼不可,感觉还是挺黑箱的。

    pypy7.3

    No_188294: (d_i: 188294, d_a: 188293, d_b: 40108)
    No_327273: (d_i: 138979, d_a: 138979, d_b: 148185)
    No_482999: (d_i: 155726, d_a: 155726, d_b: 138979)
    No_623060: (d_i: 140061, d_a: 140061, d_b: 155726)
    No_758523: (d_i: 135463, d_a: 135463, d_b: 140061)
    No_914568: (d_i: 156045, d_a: 156045, d_b: 135463)
    lxy42
        7
    lxy42  
       2020-03-13 23:14:12 +08:00
    可能是 print 执行过程中触发了 GC, 然后 GC 回收对象时又执行了__del__中的 print, 因为 print 无法重入, 导致报错.
    ppyybb
        8
    ppyybb  
       2020-03-13 23:23:45 +08:00 via iPhone
    @lithbitren 是比较黑箱,不知道这个知识点的就会被坑,所以最好不要在 del 里面做这些操作,很难控制
    monsterxx03
        9
    monsterxx03  
       2020-03-13 23:38:16 +08:00 via iPhone
    在 3.4 之前,之前如果一个 class 内部有循环引用,并重载了 del,的确会内存泄漏,celery4.1 就有这个 bug https://github.com/celery/celery/pull/4839
    felix021
        10
    felix021  
       2020-03-14 00:16:29 +08:00
    CPython 1.x 仅仅使用引用计数来实现垃圾回收。虽然引用计数易于实现和理解,但是它不足以解决循环引用的问题。于是乎在 1999 年实现了一个循环垃圾回收器,并且附加在了 Python 2 以后的版本上。
    lithbitren
        11
    lithbitren  
    OP
       2020-03-14 00:25:01 +08:00
    垃圾回收好像看到说用通过链表来解决不可到达区,但为什么会有残留呢?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2618 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 06:47 · PVG 14:47 · LAX 22:47 · JFK 01:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.