Chuyển đến nội dung chính

Thu gom rác trong Python: Khái niệm và cơ chế cốt lõi

Tìm hiểu cách Python tự động quản lý bộ nhớ bằng đếm tham chiếu và thu gom rác theo thế hệ, và nắm cách kiểm soát thủ công bằng mô-đun gc.
· 9 phút đọc

Khám phá cùng AI

Quản lý bộ nhớ là một khía cạnh quan trọng nhưng thường bị xem nhẹ trong lập trình. Nếu không xử lý đúng, nó có thể dẫn tới ứng dụng chậm, sập bất ngờ, hoặc thậm chí rò rỉ bộ nhớ. May mắn là Python xử lý vấn đề này thông qua một quy trình gọi là thu gom rác (garbage collection), một hệ thống tích hợp tự động quản lý bộ nhớ.

Vậy cơ chế thu gom rác của Python hoạt động thế nào và vì sao bạn nên quan tâm? Hiểu quy trình này là chìa khóa để viết mã hiệu quả, ít lỗi và vận hành tốt ngay cả khi dự án của bạn ngày càng phức tạp.

Kết thúc bài viết, bạn sẽ nắm được những điều cơ bản về thu gom rác trong Python và lý do nó quan trọng, hiểu cách Python dùng đếm tham chiếu và thu gom rác theo thế hệ để quản lý bộ nhớ, cũng như một số mẹo tránh rò rỉ bộ nhớ. Tôi cũng khuyến khích bạn đăng ký khóa học Writing Efficient Python Code của chúng tôi, khóa học sẽ giúp bạn biết cách phân bổ tài nguyên trong môi trường làm việc và cân nhắc chi phí phụ trợ, trong đó thu gom rác chỉ là một phần nhỏ.

Thu gom rác trong Python là gì?

Thu gom rác là cách Python tự động quản lý bộ nhớ, giúp ứng dụng chạy mượt mà bằng cách giải phóng vùng nhớ không còn được sử dụng. Nói đơn giản, nó giống như có một “nhân viên vệ sinh vô hình” dọn dẹp sau khi mã của bạn chạy, loại bỏ các đối tượng không còn cần thiết.

Thu gom rác trong Python

“Python đang thu gom rác.” Hình ảnh bởi Dall-E 2

Vì sao chúng ta cần thu gom rác trong Python?

Trong bất kỳ ứng dụng nào, các đối tượng được tạo ra để lưu trữ dữ liệu, thực hiện tính toán, hoặc quản lý tác vụ. Tuy nhiên, khi hoàn thành nhiệm vụ, chúng vẫn chiếm dung lượng bộ nhớ cho tới khi bị loại bỏ rõ ràng. Nếu các đối tượng không dùng tới này tích tụ, ứng dụng có thể tiêu thụ nhiều bộ nhớ hơn cần thiết, dẫn đến hiệu năng giảm và có thể sập.

Thu gom rác ngăn chặn các kịch bản như vậy. Nó tự động phát hiện khi một đối tượng không còn cần thiết (không còn phần nào của mã tham chiếu tới đối tượng) và an toàn loại bỏ đối tượng khỏi bộ nhớ. Quy trình này giúp:

  • Ngăn rò rỉ bộ nhớ: Bằng cách tự động dọn dẹp các đối tượng không dùng tới, thu gom rác giảm nguy cơ rò rỉ bộ nhớ, khi vùng nhớ không còn cần thiết nhưng không được giải phóng.
  • Tối ưu hiệu năng: Bằng cách giải phóng bộ nhớ, thu gom rác giúp duy trì hiệu năng ứng dụng, đặc biệt trong các chương trình chạy lâu hoặc xử lý lượng dữ liệu lớn.
  • Đơn giản hóa phát triển: Vì Python tự động quản lý bộ nhớ, nhà phát triển có thể tập trung vào viết mã thay vì quản lý bộ nhớ.

Python triển khai thu gom rác tự động như thế nào

Hệ thống thu gom rác của Python khá phức tạp, được thiết kế để tự động quản lý bộ nhớ, cho phép nhà phát triển tập trung vào viết mã thay vì lo lắng về bộ nhớ. Python chủ yếu dùng hai cơ chế để triển khai thu gom rác: đếm tham chiếu và thu gom rác theo thế hệ. Hai cơ chế này phối hợp để đảm bảo bộ nhớ được quản lý hiệu quả, giảm thiểu khả năng rò rỉ bộ nhớ và tối ưu hiệu năng ứng dụng.

Trước khi đi sâu vào các cơ chế này, cần lưu ý rằng quy trình thu gom rác này áp dụng nhiều nhất cho CPython, bản triển khai Python được dùng nhiều nhất. Trong khi các bản triển khai khác như PyPy hoặc Jython có thể xử lý thu gom rác khác đi, CPython dựa rất nhiều vào hai phương pháp này để giúp ứng dụng của bạn chạy trơn tru.

Đếm tham chiếu

Đếm tham chiếu là phương pháp nền tảng Python dùng để quản lý bộ nhớ. Về cốt lõi, đếm tham chiếu liên quan đến việc theo dõi số lượng tham chiếu (hoặc con trỏ) tới một đối tượng trong bộ nhớ. Mỗi khi tạo một tham chiếu mới tới đối tượng, Python tăng bộ đếm tham chiếu của đối tượng đó. Ngược lại, khi một tham chiếu bị xóa hoặc ra khỏi phạm vi, Python giảm bộ đếm. Cách hoạt động như sau:

  • Theo dõi tham chiếu: Mỗi đối tượng trong Python có một bộ đếm tham chiếu, được cập nhật mỗi khi đối tượng được tham chiếu hoặc bỏ tham chiếu. Ví dụ, gán một đối tượng cho biến hoặc truyền nó vào hàm sẽ tăng bộ đếm; xóa biến sẽ giảm bộ đếm.
  • Giải phóng bộ nhớ: Khi bộ đếm tham chiếu của một đối tượng giảm về 0, tức không còn phần nào của mã sử dụng đối tượng, Python tự động giải phóng bộ nhớ mà nó chiếm dụng.

Dù hiệu quả, đếm tham chiếu có hạn chế. Lớn nhất là không xử lý được tham chiếu vòng, xảy ra khi hai hoặc nhiều đối tượng tham chiếu lẫn nhau tạo thành vòng lặp. Khi đó, bộ đếm không bao giờ về 0, khiến bộ nhớ không được thu hồi. Đây là lúc thu gom rác theo thế hệ phát huy tác dụng.

Thu gom rác theo thế hệ

Để khắc phục hạn chế của đếm tham chiếu, Python còn dùng thu gom rác theo thế hệ. Phương pháp nâng cao này được thiết kế để xử lý tham chiếu vòng và cải thiện hiệu quả quản lý bộ nhớ. Ý tưởng cốt lõi là hầu hết đối tượng hoặc tồn tại ngắn hạn (tạm thời) hoặc dài hạn (bền vững). Bằng cách phân loại đối tượng theo “tuổi”, Python tối ưu quy trình thu gom rác. Cách hoạt động như sau:

  • Các thế hệ: Bộ thu gom rác của Python tổ chức đối tượng thành ba thế hệ: Thế hệ 0 (trẻ nhất), Thế hệ 1 (trung niên), và Thế hệ 2 (già nhất). Đối tượng mới được đặt vào Thế hệ 0; nếu “sống sót” qua các lần thu gom, chúng được chuyển lên thế hệ tiếp theo.

  • Ưu tiên đối tượng trẻ: Trình gom rác chạy thường xuyên hơn trên các đối tượng trẻ (Thế hệ 0) vì chúng có khả năng nhanh chóng trở nên không cần thiết. Khi đối tượng “già” đi và được chuyển lên thế hệ cao hơn, tần suất thu gom giảm. Cách tiếp cận này giảm chi phí của thu gom rác bằng cách tập trung vào các đối tượng có khả năng sớm bị loại bỏ.

  • Xử lý tham chiếu vòng: Thu gom rác theo thế hệ đặc biệt hiệu quả trong việc nhận diện và thu gom các đối tượng tham gia vào tham chiếu vòng. Trong quá trình thu gom, bộ gom rác của Python có thể phát hiện các vòng này và thu hồi bộ nhớ, ngăn rò rỉ do tham chiếu vòng gây ra.

Cách bạn có thể kích hoạt thu gom rác trong Python thủ công 

Dù hệ thống thu gom rác của Python được thiết kế để tự động quản lý bộ nhớ, vẫn có những tình huống việc quản lý thủ công sẽ hữu ích. Hãy cùng xem.

Chủ động khởi chạy thu gom rác trong Python

Trong hầu hết trường hợp, bộ gom rác của Python chạy tự động, dọn dẹp đối tượng không dùng tới mà không cần can thiệp. Tuy nhiên, đôi lúc bạn có thể muốn kích hoạt thủ công để giải phóng bộ nhớ. Điều này đặc biệt hữu ích trong các ứng dụng chạy lâu hoặc quy trình ngốn bộ nhớ, nơi cần quản lý chặt chẽ mức sử dụng bộ nhớ.

Để kích hoạt thu gom rác thủ công, bạn có thể dùng hàm gc.collect(). Hàm này buộc bộ gom rác chạy ngay, thu hồi vùng nhớ không còn sử dụng. Cách hoạt động như sau:

import gc
# Trigger garbage collection manually
gc.collect()

Khi bạn gọi gc.collect(), bộ gom rác của Python sẽ thực hiện một lần thu gom toàn diện, quét tất cả đối tượng trong bộ nhớ và giải phóng những đối tượng không còn được tham chiếu. Điều này hữu ích trong các tình huống như:

  • Ứng dụng ngốn bộ nhớ: Trong các ứng dụng xử lý lượng dữ liệu lớn hoặc tạo nhiều đối tượng, kích hoạt thu gom rác thủ công có thể giúp giải phóng bộ nhớ tại những thời điểm quan trọng, giảm nguy cơ phình bộ nhớ.
  • Quy trình chạy lâu: Với dịch vụ hoặc ứng dụng chạy dài hạn như máy chủ hoặc tác vụ nền, quản lý thu gom rác thủ công có thể giúp duy trì mức sử dụng bộ nhớ ổn định, bảo đảm ứng dụng phản hồi tốt và hiệu quả theo thời gian.

Tuy nhiên, cần dùng gc.collect() một cách tiết chế. Gọi thủ công thường xuyên có thể tạo chi phí thừa, vì quy trình này tiêu tốn tài nguyên. Tốt nhất chỉ dùng trong các kịch bản cụ thể khi bạn đã nhận diện vấn đề bộ nhớ hoặc cần giải phóng bộ nhớ đúng thời điểm.

Tắt thu gom rác

Trong một số trường hợp, bạn có thể muốn tạm thời tắt cơ chế thu gom rác tự động của Python. Điều này hữu ích khi chi phí của thu gom rác có thể ảnh hưởng tiêu cực tới hiệu năng, chẳng hạn ứng dụng thời gian thực, đoạn mã quan trọng về hiệu năng, hoặc trong các thao tác bạn muốn giảm thiểu gián đoạn.

Để tắt bộ gom rác, bạn có thể dùng hàm gc.disable():

import gc
# Disable automatic garbage collection
gc.disable()

Khi thu gom rác bị tắt, Python sẽ ngừng tự động thu gom và giải phóng các đối tượng không dùng tới. Điều này có thể giúp hiệu năng dễ dự đoán hơn trong một số tình huống, vì ngăn bộ gom rác chạy bất ngờ trong lúc thực thi các thao tác quan trọng.

Tuy nhiên, tắt thu gom rác cũng có rủi ro:

  • Rò rỉ bộ nhớ: Không có thu gom rác, các đối tượng không dùng tới sẽ tồn tại trong bộ nhớ cho đến khi tiến trình kết thúc, có thể dẫn đến rò rỉ bộ nhớ. Điều này đặc biệt nghiêm trọng trong ứng dụng chạy lâu, nơi mức sử dụng bộ nhớ có thể tăng không kiểm soát.
  • Tham chiếu vòng: Vì tham chiếu vòng không được tự động xử lý nếu không có thu gom rác, việc tắt bộ gom rác có thể làm trầm trọng thêm vấn đề bộ nhớ nếu trong mã có các vòng tham chiếu.

Vì các lý do đó, điều quan trọng là bật lại thu gom rác sau khi đoạn mã quan trọng về hiệu năng đã chạy xong. Bạn có thể bật lại bằng gc.enable():

# Re-enable automatic garbage collection
gc.enable()

Dưới đây là một số thực tiễn tốt khi thu gom rác thủ công:

  • Dùng tiết chế: Chỉ áp dụng thu gom rác thủ công khi thật sự cần thiết. Lạm dụng có thể làm giảm hiệu năng.
  • Kết hợp với profiling: Trước khi kích hoạt thủ công hoặc tắt thu gom rác, hãy cân nhắc profiling ứng dụng để hiểu mô hình sử dụng bộ nhớ. Điều này giúp quyết định có cần can thiệp thủ công không và can thiệp ở đâu sẽ hiệu quả nhất.
  • Bật lại khi cần: Nếu bạn tắt thu gom rác, hãy nhớ bật lại ngay khi đoạn mã quan trọng hoàn tất. Điều này đảm bảo bộ nhớ được quản lý hiệu quả về lâu dài.

Bằng cách hiểu khi nào và như thế nào để dùng thu gom rác thủ công, bạn có thể kiểm soát tốt hơn việc quản lý bộ nhớ của ứng dụng. Điều này không chỉ giúp tối ưu hiệu năng mà còn ngăn các vấn đề liên quan tới bộ nhớ có thể ảnh hưởng đến độ ổn định.

Trong phần tiếp theo, chúng ta sẽ khám phá các vấn đề thu gom rác thường gặp trong Python và đưa ra mẹo thực tiễn để gỡ lỗi và khắc phục.

Những lưu ý thực tiễn cho nhà phát triển Python

Quản lý bộ nhớ hiệu quả là yếu tố then chốt để viết các ứng dụng Python hiệu năng cao. Dù hệ thống thu gom rác của Python xử lý phần lớn công việc tự động, vẫn có những bước thực tiễn giúp tránh lỗi thường gặp và tối ưu sử dụng bộ nhớ. Phần này sẽ trình bày chiến lược tránh rò rỉ bộ nhớ, xử lý tham chiếu vòng và quản lý số lượng lớn đối tượng trong ứng dụng Python.

Tránh rò rỉ bộ nhớ

Rò rỉ bộ nhớ xảy ra khi các đối tượng không còn cần thiết nhưng không được giải phóng đúng cách, khiến ứng dụng tiêu thụ ngày càng nhiều bộ nhớ. Điều này có thể làm giảm hiệu năng hoặc thậm chí gây sập ứng dụng. Để theo dõi đối tượng trong bộ nhớ, bạn có thể dùng mô-đun gc của Python. Bằng cách quan sát số lượng đối tượng trong bộ nhớ, bạn có thể phát hiện mức tăng bất thường, dấu hiệu khả dĩ của rò rỉ bộ nhớ:

import gc

# Get a list of all objects tracked by the garbage collector
all_objects = gc.get_objects()

print(f"Number of tracked objects: {len(all_objects)}")

Nếu nghi ngờ một đối tượng không được thu gom, hàm gc.get_referrers() giúp xác định điều gì đang giữ đối tượng trong bộ nhớ. Hàm này trả về danh sách đối tượng đang tham chiếu tới đối tượng đã cho, cho phép bạn xác định các tham chiếu đó có cần thiết không hoặc có thể loại bỏ được không:

some_object = ...
referrers = gc.get_referrers(some_object)

print(f"Object is being referenced by: {referrers}")

Một cách khác để tránh rò rỉ bộ nhớ là dùng tham chiếu yếu, đặc biệt trong các tình huống caching. Mô-đun weakref cho phép bạn tạo tham chiếu không làm tăng bộ đếm tham chiếu của đối tượng:

import weakref

class MyClass:
    pass
obj = MyClass()
weak_obj = weakref.ref(obj)

print(weak_obj())  # Access the object

del obj  # Delete the strong reference
print(weak_obj())  # None, as the object is now collected

Xử lý tham chiếu vòng

Tham chiếu vòng xảy ra khi hai hoặc nhiều đối tượng tham chiếu lẫn nhau, tạo thành một vòng khiến bộ đếm tham chiếu không bao giờ về 0. Điều này có thể dẫn đến rò rỉ bộ nhớ nếu bộ gom rác không phát hiện và thu gom được các đối tượng này.

Bộ gom rác theo thế hệ của Python được thiết kế để xử lý tham chiếu vòng, nhưng tốt nhất là không tạo vòng không cần thiết ngay từ đầu. Với mô-đun gc, bạn có thể phát hiện đối tượng nằm trong các vòng tham chiếu bằng cách kích hoạt thu gom và kiểm tra danh sách gc.garbage, chứa các đối tượng thuộc vòng và không thể thu gom:

import gc

# Trigger garbage collection and get the number of uncollectable objects
gc.collect()

uncollectable_objects = gc.garbage
print(f"Number of uncollectable objects: {len(uncollectable_objects)}")

Để ngăn tham chiếu vòng, hãy cân nhắc phá vỡ vòng bằng cách loại bỏ rõ ràng các tham chiếu khi không còn cần. Bạn có thể đặt tham chiếu về None hoặc dùng tham chiếu yếu, vốn đặc biệt hiệu quả để tránh vòng tham chiếu mạnh. Ngoài ra, đơn giản hóa thiết kế cấu trúc dữ liệu và mối quan hệ đối tượng cũng giúp giảm khả năng tạo vòng.

Quản lý số lượng lớn đối tượng

Việc tạo và hủy số lượng lớn đối tượng trong thời gian ngắn có thể gây áp lực lên bộ gom rác của Python. Hãy nghĩ đến các ứng dụng xử lý bộ dữ liệu lớn, dữ liệu thời gian thực, hoặc thực hiện các tình huống phức tạp.

Một ý tưởng là gom theo lô việc tạo và xóa đối tượng. Thay vì tạo và hủy từng đối tượng một, gom các thao tác này theo lô có thể giảm tần suất thu gom rác và cho phép bộ gom rác hoạt động hiệu quả hơn. Ví dụ:

objects = []

for i in range(1000):
    obj = SomeClass()
    objects.append(obj)

# Process all objects at once, then delete them
del objects[:]
gc.collect()  # Optionally trigger a manual garbage collection

Một cân nhắc khác là tối ưu vòng đời đối tượng bằng cách đảm bảo đối tượng hoặc là ngắn hạn hoặc là dài hạn. Đối tượng ngắn hạn sẽ được thu gom nhanh, còn đối tượng dài hạn sẽ được chuyển lên thế hệ cao hơn, nơi tần suất thu gom ít hơn.

Trong các kịch bản đối tượng được tạo và hủy thường xuyên, bể đối tượng (object pool) có thể là kỹ thuật hữu ích. Bể đối tượng tái sử dụng một số lượng cố định đối tượng, giảm áp lực lên bộ gom rác và cải thiện hiệu năng trong môi trường hạn chế bộ nhớ. Đây là ví dụ về triển khai bể đối tượng:

class ObjectPool:
    def __init__(self, size):
        self.pool = [SomeClass() for _ in range(size)]
    def get(self):
        return self.pool.pop()
    def release(self, obj):
        self.pool.append(obj)

Bể đối tượng đặc biệt hữu ích trong ứng dụng thời gian thực hoặc game, nơi hiệu năng là then chốt và chi phí tạo/hủy đối tượng thường xuyên có thể đáng kể.

Kết luận

Trong bài viết này, chúng ta đã tìm hiểu hệ thống thu gom rác của Python, từ cách nó tự động quản lý bộ nhớ đến các can thiệp thủ công bạn có thể thực hiện để cải thiện hiệu năng. Sự kết hợp giữa đếm tham chiếu và thu gom rác theo thế hệ giúp Python quản lý bộ nhớ hiệu quả, dù đôi khi can thiệp thủ công vẫn có lợi. Bằng cách làm theo các thực tiễn tốt trong hướng dẫn này, bạn có thể tránh rò rỉ bộ nhớ, tăng hiệu năng và kiểm soát tốt hơn cách ứng dụng xử lý bộ nhớ.

Nếu bạn muốn tìm hiểu sâu hơn về quản lý bộ nhớ và tối ưu mã tổng thể, có nhiều tài nguyên DataCamp chất lượng sẵn có. Bạn có thể đọc các bài hướng dẫn How to Write Memory-Efficient Classes in Python và Memory Profiling in Python, cả hai đều hữu ích để xác định nút thắt hiệu năng. Hơn nữa, học cách viết mã Python hiệu quả luôn hữu ích cho mọi sự nghiệp. 

Samuel Shaibu

Tác giả

Samuel Shaibu

Chuyên gia dữ liệu và tác giả dày dạn kinh nghiệm, đam mê hỗ trợ những người đang theo đuổi con đường trở thành chuyên gia trong lĩnh vực dữ liệu.

Câu hỏi thường gặp về Thu gom rác trong Python

Làm thế nào tôi có thể theo dõi mức sử dụng bộ nhớ trong các ứng dụng Python?

Bằng cách sử dụng các công cụ như memory_profiler, tracemalloc và objgraph. Các công cụ này giúp theo dõi mức tiêu thụ bộ nhớ, xác định rò rỉ bộ nhớ và tối ưu việc sử dụng bộ nhớ.

Làm thế nào tôi có thể kích hoạt thu gom rác thủ công trong Python?

Bạn có thể làm điều này bằng cách sử dụng mô-đun gc. Các hàm như gc.collect() cho phép bạn ép bộ gom rác chạy một chu kỳ, hữu ích cho việc gỡ lỗi hoặc tối ưu sử dụng bộ nhớ trong các tình huống cụ thể.

Có thể tắt cơ chế thu gom rác của Python không?

Có, bạn có thể tắt cơ chế thu gom rác của Python bằng mô-đun gc. Gọi gc.disable() sẽ ngăn bộ gom rác chạy.

Chủ đề

Liên quan