Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Garbage Collection trong Python
Trong bài viết này, bạn sẽ tìm hiểu về cơ chế hoạt động của bộ thu gom rác trong Python và cách bạn có thể tương tác với nó thông qua lập trình để tối ưu hóa việc quản lý bộ nhớ.
Giới thiệu về garbage Collection trong Python
Trong các ngôn ngữ như C/C++, bạn hoàn toàn chịu trách nhiệm quản lý bộ nhớ của chương trình. Tuy nhiên, trong Python, bạn không cần phải tự quản lý bộ nhớ vì Python tự động làm điều này cho bạn.Trong mình về tham chiếu, bạn đã biết rằng Python Memory Manager theo dõi các tham chiếu của đối tượng. Python sẽ phá hủy đối tượng và thu hồi bộ nhớ khi số lượng tham chiếu của đối tượng đó bằng 0.
Tuy nhiên, việc đếm tham chiếu không phải lúc nào cũng hoạt động chính xác. Ví dụ, khi bạn có một đối tượng tham chiếu đến chính nó hoặc hai đối tượng tham chiếu lẫn nhau, điều này tạo ra cái gọi là tham chiếu vòng tròn (circular references).
Khi Python Memory Manager không thể loại bỏ các đối tượng với tham chiếu vòng tròn, nó sẽ gây ra hiện tượng rò rỉ bộ nhớ (memory leak).Đây là lý do bộ thu gom rác ra đời để khắc phục các tham chiếu vòng tròn.Python cho phép bạn tương tác với bộ thu gom rác thông qua mô-đun tích hợp sẵn gc
.
Bài viết này được đăng tại [free tuts .net]
Tương tác với garbage Collection của Python
Trong ví dụ này, mình sẽ tạo một tham chiếu vòng tròn giữa hai thể hiện của lớp A
và B
. Sau đó, mìnhsẽ sử dụng bộ thu gom rác để phá hủy các đối tượng trong tham chiếu vòng tròn.
Đầu tiên, import các mô-đun gc
và ctypes
, và định nghĩa hai hàm để đếm tham chiếu và kiểm tra xem một đối tượng có tồn tại trong bộ nhớ không:
import gc import ctypes def ref_count(address): return ctypes.c_long.from_address(address).value def object_exists(object_id): for object in gc.get_objects(): if id(object) == object_id: return True return False
Trong mã này, hàm ref_count()
trả về số lượng tham chiếu của một đối tượng được chỉ định bởi địa chỉ bộ nhớ của nó. Và hàm object_exists()
trả về True nếu một đối tượng tồn tại trong bộ nhớ.
Tiếp theo, tạo hai lớp A
và B
tham chiếu lẫn nhau:
class A: def __init__(self): self.b = B(self) print(f'A: {hex(id(self))}, B: {hex(id(self.b))}') class B: def __init__(self, a): self.a = a print(f'B: {hex(id(self))}, A: {hex(id(self.a))}')
Sau đó, tắt bộ thu gom rác bằng cách gọi hàm disable()
:
gc.disable()
Tiếp theo, tạo một thể hiện mới của lớp A
mà cũng tự động tạo ra một thể hiện của B
:
a = A()
Kết quả:
B: 0x20fccd148e0, A: 0x20fccd75c40 A: 0x20fccd75c40, B: 0x20fccd148e0
Tiếp theo, định nghĩa hai biến để giữ địa chỉ bộ nhớ của các thể hiện của A
và B
. Các biến này theo dõi địa chỉ bộ nhớ của các thể hiện của A
và B
khi biến a
tham chiếu đến một đối tượng khác.
a_id = id(a) b_id = id(a.b)
Tiếp theo, hiển thị số lượng tham chiếu của các thể hiện của A
và B
:
print(ref_count(a_id)) # 2 print(ref_count(b_id)) # 1
Thể hiện của A
có hai tham chiếu là biến a
và thể hiện của B
. Và thể hiện của B
có một tham chiếu là thể hiện của A
.
Tiếp theo, kiểm tra xem cả hai thể hiện của A
và B
có trong bộ nhớ không:
print(object_exists(a_id)) # True print(object_exists(b_id)) # True
Cả hai đều tồn tại.
Sau đó, gán biến a
thành None
:
a = None
Tiếp theo, lấy số lượng tham chiếu của thể hiện của A
và B
:
print(ref_count(a_id)) # 1 print(ref_count(b_id)) # 1
Bây giờ, cả hai tham chiếu của thể hiện của A
và B
đều là 1.
Tiếp theo, kiểm tra xem các thể hiện có tồn tại không:
print(object_exists(a_id)) # True print(object_exists(b_id)) # True
Cả hai vẫn tồn tại như mong đợi.
Tiếp theo, khởi động bộ thu gom rác:
gc.collect()
Khi bộ thu gom rác chạy, nó có thể phát hiện tham chiếu vòng tròn, phá hủy các đối tượng và thu hồi bộ nhớ.
Tiếp theo, kiểm tra xem các thể hiện của A
và B
có tồn tại không:
print(object_exists(a_id)) # False print(object_exists(b_id)) # False
Cả hai không còn tồn tại do bộ thu gom rác.
Cuối cùng, lấy số lượng tham chiếu của các thể hiện của A
và B
:
print(ref_count(a_id)) # 0 print(ref_count(b_id)) # 0
Đưa tất cả vào một đoạn mã hoàn chỉnh:
import gc import ctypes def ref_count(address): return ctypes.c_long.from_address(address).value def object_exists(object_id): for object in gc.get_objects(): if id(object) == object_id: return True return False class A: def __init__(self): self.b = B(self) print(f'A: {hex(id(self))}, B: {hex(id(self.b))}') class B: def __init__(self, a): self.a = a print(f'B: {hex(id(self))}, A: {hex(id(self.a))}') # disable the garbage collector gc.disable() a = A() a_id = id(a) b_id = id(a.b) print(ref_count(a_id)) # 2 print(ref_count(b_id)) # 1 print(object_exists(a_id)) # True print(object_exists(b_id)) # True a = None print(ref_count(a_id)) # 1 print(ref_count(b_id)) # 1 print(object_exists(a_id)) # True print(object_exists(b_id)) # True # run the garbage collector gc.collect() # check if object exists print(object_exists(a_id)) # False print(object_exists(b_id)) # False # reference count print(ref_count(a_id)) # 0 print(ref_count(b_id)) # 0