首页 > 编程知识 正文

redis垃圾回收,python 线程会自动回收吗

时间:2023-05-03 11:51:34 阅读:131684 作者:568

每天反省我的身体,我是什么样的垃圾?

生活中饱受垃圾分类之苦的各位,今天就来谈谈Python的垃圾回收机制

我们知道Python程序在运行时需要在内存中留出空间来存储运行时发生的临时变量。计算完成后,它会将结果输出到非易失性存储器。 如果数据量太大,内存空间管理不良就容易产生内存输出(oom ),俗称爆炸内存,操作系统可能会中止程序。

对于服务器,内存管理更为重要,因为它旨在避免中断此类系统。 否则,很容易发生内存泄漏。 什么是内存泄漏?

这里的泄露并不是指你的内存出现了信息安全问题,被恶意程序利用了,而是指程序本身没有设计,无法释放不再使用的内存。

内存泄漏并不是指你的内存物理消失,而是指分配了包含代码的内存后,由于设计错误而失去了对内存的控制,导致内存浪费。

那么Python是怎么解决这些问题的呢? 换句话说,对于不再使用的内存空间,Python是通过什么机制回收这些空间的呢?

引用数

我重复了好几次,在Python上一切都是对象。 因此,你看到的所有变量本质上都是对象的指针。

那么,你怎么知道一个对象,会不会永远被呼叫?

当对象的参照计数(指针的数目)为0时,该对象决不能到达,这当然也成为垃圾,表示该对象需要被回收。

请看一个例子:

导入操作系统

导入PS util

#显示当前python程序消耗的内存大小

efshow_memory_info(hint ) :

pid=os.getpid (

p=psutil.Process(pid )

info=p.memory_full_info (

memory=info.uss/1024./1024

print((memoryused: ) MB ).format (hint,memory ) )。

代码def func (复制:

show_memory_info(initial ) )。

a=[IforIinrange(1000000 ) ]

show _ memory _ info (已关联) )。

func () )

show _ memory _ info (完成) )。

# # # #

initialmemoryused 336047.19140625 MB

afteracreatedmemoryused 3360433.91015625 MB

finishedmemoryused :48.109375 MB

复制代码

在此示例中,通过调用函数func (),可以看到在创建列表a后,内存使用量迅速增加到433 MB。 函数调用结束后,内存恢复正常。

这是因为在函数内部声明的列表a是局部变量,在函数返回后,局部变量的参照被删除; 此时,如果列表a指向的对象引用数为0,则Python将执行垃圾回收,从而返回以前消耗的大量内存。

理解了这个原理后,稍微修改一下代码:

deffunc(:

show_memory_info(initial ) )。

全球a

a=[IforIinrange(1000000 ) ]

show _ memory _ info (已关联) )。

func () )

show _ memory _ info (完成) )。

# # # #

initialmemoryused 336048.88671875 MB

afteracreatedmemoryused 3360433.94921875 MB

finishedmemoryused :433.94921875 MB

复制代码

在新代码中,global a表示将a声明为全局变量。 在中,即使函数返回,对列表的引用仍然保留,因此不会进行垃圾回收,仍然占用大量内存。

同样,如果在主程序中返回生成的列表并将其接收,则引用仍然存在,不会触发垃圾回收,并且会消耗大量内存。

deffunc(:

show_memory_info(initial ) )。

a=[IforIinderange(1000000 ) ]

show _ memory _ info (已关联) )。

返回a

a=func ()

show_memory_info

('finished')

########## 输出 ##########

initial memory used: 47.96484375 MB

after a created memory used: 434.515625 MB

finished memory used: 434.515625 MB

复制代码

这是最常见的几种情况。由表及里,下面,我们深入看一下 Python 内部的引用计数机制。老规矩,先来看代码:

import sys

a = []

# 两次引用,一次来自 a,一次来自 getrefcount

print(sys.getrefcount(a))

def func(a):

# 四次引用,a,python 的函数调用栈,函数参数,和 getrefcount

print(sys.getrefcount(a))

func(a)

# 两次引用,一次来自 a,一次来自 getrefcount,函数 func 调用已经不存在

print(sys.getrefcount(a))

########## 输出 ##########

2

4

2

复制代码

简单介绍一下,sys.getrefcount() 这个函数,可以查看一个变量的引用次数。这段代码本身应该很好理解,不过别忘了,getrefcount 本身也会引入一次计数。

另一个要注意的是,在函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。

import sys

a = []

print(sys.getrefcount(a)) # 两次

b = a

print(sys.getrefcount(a)) # 三次

c = b

d = b

e = c

f = e

g = d

print(sys.getrefcount(a)) # 八次

########## 输出 ##########

2

3

8

复制代码

看到这段代码,需要你稍微注意一下,a、b、c、d、e、f、g 这些变量全部指代的是同一个对象,而 sys.getrefcount() 函数并不是统计一个指针,而是要统计一个对象被引用的次数,所以最后一共会有八次引用。

理解引用这个概念后,引用释放是一种非常自然和清晰的思想。相比 C 语言里,你需要使用 free 去手动释放内存,Python 的垃圾回收在这里可以说是省心省力了。

不过,我想还是会有人问,如果我偏偏想手动释放内存,应该怎么做呢?

方法同样很简单。你只需要先调用 del a 来删除对象的引用;然后强制调用 gc.collect(),清除没有引用的对象,即可手动启动垃圾回收。

import gc

show_memory_info('initial')

a = [i for i in range(10000000)]

show_memory_info('after a created')

del a

gc.collect()

show_memory_info('finish')

print(a)

########## 输出 ##########

initial memory used: 48.1015625 MB

after a created memory used: 434.3828125 MB

finish memory used: 48.33203125 MB

---------------------------------------------------------------------------

NameError Traceback (most recent call last)

in

11

12 show_memory_info('finish')

---> 13 print(a)

NameError: name 'a' is not defined

复制代码

到这里,是不是觉得垃圾回收非常简单呀?

我想,肯定有人觉得自己都懂了,那么,如果此时有面试官问:引用次数为 0 是垃圾回收启动的充要条件吗?还有没有其他可能性呢?

这个问题,你能回答的上来吗?

循环引用

如果你也被困住了,别急。我们不妨小步设问,先来思考这么一个问题:如果有两个对象,它们互相引用,并且不再被别的对象所引用,那么它们应该被垃圾回收吗?

def func():

show_memory_info('initial')

a = [i for i in range(10000000)]

b = [i for i in range(10000000)]

show_memory_info('after a, b created')

a.append(b)

b.append(a)

func()

show_memory_info('finished')

########## 输出 ##########

initial memory used: 47.984375 MB

after a, b created memory used: 822.73828125 MB

finished memory used: 821.73046875 MB

复制代码

这里,a 和 b 互相引用,并且,作为局部变量,在函数 func 调用结束后,a 和 b 这两个指针从程序意义上已经不存在了。但是,很明显,依然有内存占用!为什么呢?因为互相引用,导致它们的引用数都不为 0。

试想一下,如果这段代码出现在生产环境中,哪怕 a 和 b 一开始占用的空间不是很大,但经过长时间运行后,Python 所占用的内存一定会变得越来越大,最终撑爆服务器,后果不堪设想。

当然,有人可能会说,互相引用还是很容易被发现的呀,问题不大。可是,更隐蔽的情况是出现一个引用环,在工程代码比较复杂的情况下,引用环还真不一定能被轻易发现。

那么,我们应该怎么做呢?

事实上,Python 本身能够处理这种情况,我们刚刚讲过的,可以显式调用 gc.collect() ,来启动垃圾回收。

import gc

def func():

show_memory_info('initial')

a = [i for i in range(10000000)]

b = [i for i in range(10000000)]

show_memory_info('after a, b created')

a.append(b)

b.append(a)

func()

gc.collect()

show_memory_info('finished')

########## 输出 ##########

initial memory used: 49.51171875 MB

after a, b created memory used: 824.1328125 MB

finished memory used: 49.98046875 MB

复制代码

调试内存泄漏

虽然有了自动回收机制,但这也不是万能的,难免还是会有漏网之鱼。内存泄漏是我们不想见到的,而且还会严重影响性能。有没有什么好的调试手段呢?

答案当然是肯定的,接下来我就为你介绍一个“怕孤单的鞋垫”。

它就是 objgraph,一个非常好用的可视化引用关系的包。在这个包中,我主要推荐两个函数,第一个是 show_refs(),它可以生成清晰的引用关系图。

通过下面这段代码和生成的引用调用图,你能非常直观地发现,有两个 list 互相引用,说明这里极有可能引起内存泄露。这样一来,再去代码层排查就容易多了。import objgraph

import objgraph

a = [1, 2, 3]

b = [4, 5, 6]

a.append(b)

b.append(a)

objgraph.show_refs([a])

复制代码

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。