当前位置: 首页 > 科技观察

为什么Python需要较少关注垃圾回收?

时间:2023-03-15 08:55:34 科技观察

垃圾回收作为Python程序员,我们也很开心。我们在日常生活中不需要关注内存管理和垃圾回收,因为CPython解释器有自己的机制来处理。那么,为什么垃圾回收在Python世界中不是那么重要?这是因为Python自带的解释器会自动处理垃圾回收,在大多数情况下不需要人为干预。另外,大家对Python的共识就是开发效率。因为是胶水语言,很多场景下高性能和内存问题并不突出,而且现在服务器资源很便宜,人力资源很贵。使用Python进行web开发,工作多年不会遇到内存管理和垃圾回收。几乎所有的Web应用程序都使用多进程模型。一是会有周期性超时重启的机制,二是每次上线操作也会重启进程。因此,不会有一个进程长期驻留,导致其占用大量内存,造成内存泄漏。因此,GC的缺陷基本上不会对Web开发造成太大的影响。而且CPython也足够完善,基本不会出现内存泄露等问题。大多数场景下,内存使用异常是开发者的错误使用或误判造成的。引用计数Python的垃圾回收是基于引用技术的,所以理解引用计数也很重要。引用计数的原理是,当一个对象的引用被创建或复制时,该对象的引用计数加1;当一个对象的引用被销毁时,该对象的引用计数减1;当对象的引用计数减少到0时,表示该对象还没有被任何人使用,它占用的内存可以立即释放。引用计数机制的特点是实时性较好,但是引用计数会存在循环引用问题。比如A引用B,B引用A,这样每个对象的引用计数都不为0,那么A和B占用的内存资源就永远不会被回收。因此需要一些回收算法来解决这个问题,Python使用了标记清除和分代回收机制。https://mp.weixin.qq.com/s/KJm4cIZ8Ms96r3N9rT8M9wmark-clear上面我们说了mark-clear是为了解决循环引用的问题。在最理想的情况下,比如有两个对象A和B,A有对B的引用,那么B的引用计数会减1。然后跟着对B的引用,因为B有对B的引用A,同时也将A的引用计数减1。这样,引用计数中的循环引用环就去掉了。然而,还有另一个问题。假设对象A引用了对象C,而C没有引用A,如果C的引用计数减1,最后A没有被回收,显然我们把C的引用计数减1了错误。这将导致在将来的某个时候对C的悬空引用。这就需要我们在C没有被删除的情况下,复用C的引用计数。如果采用这样的方案,那么维护这个引用计数的复杂度将成倍增加。而这个mark-and-sweep使用了一个更好的实践来解决这个问题。标记移除采用了更好的做法,它不改变真正的引用计数,而是将集合中对象的引用计数复制一份,并改变对象引用的副本。对副本的任何更改都不会影响对象生命周期的维护。Recyclingbygenerations世代回收是采访中经常被问到的一个问题。分代回收的核心思想是,对象存活的时间越长,越不可能成为垃圾,越不应该被回收。并且Python将所有的对象分为3代:0、1、2,所有新创建的对象都是0代对象。但是,当某一代的对象经过垃圾回收后仍然存活时,则将其归类为下一代的对象,即第1代或第2代。可以使用如下代码查看预值世代恢复。通常,返回一个包含三个值的元组,默认值为(700,10,10)。第一个值700表示从上次垃圾回收到现在分配的内存数减去释放的内存数。如果这个值达到700,会收集第一代的垃圾对象,第二个值加1。当第二个值增加到10时,会收集第一代和第二代的垃圾对象,并且第三个值会增加1。当第三个值增加到10时,所有三代都会被回收,然后初始化为(0,0,0)并继续计数。需要注意的是,如果没有非常必要的场景,这个分代回收的默认值通常是不需要我们人为改变的。In[1]:importgcIn[2]:gc.get_threshold()Out[2]:(700,10,10)强制回收上面介绍了Python的自动垃圾回收机制,Python也支持在某个特定时间点时刻,使用gc.collect()方法强制回收。不,通常我们不应用强制回收,而是使用以下禁用垃圾收集的方法。禁用垃圾回收这个垃圾回收机制不是很好,为什么要禁用它。通常我们禁用GC的场景之一是某段代码需要加载大量的原始数据,尤其是有大量的创建、删除对象等操作时。即当执行某段代码时,会自动触发多次垃圾回收。但是,我们需要知道,Python在进行垃圾回收时,会暂停当前的工作。所以,这种比较耗时的工作会拖累我们程序的运行时间。那么我们该怎么办?我们通常在执行这段代码前先禁用垃圾回收,执行完后再手动启用。熟悉开源项目的同学可以看到有些项目使用了gc.set_threshold(0)而不是gc.disable。是因为有些第三方库会隐式开启GC让gc.disable失效,而使用gc.set_threshold(0)不会让第三方库开启垃圾回收,除非我们想开启它。gc.disable()做点什么gc.enable()