Python의 메모리 관리 (기본)

L-cloud·2022년 10월 9일
0

파이썬

목록 보기
1/4
post-thumbnail

GIL이 무엇인지 안다는 가정하에 적은 글입니다.
GIL 알아보기

Cpython은 C와 다르게 메모리 관리를 직접할 필요가 없다. 기본적으로 reference counting으로 메모리 관리를 한다. 예를 들어 보자.

처음 x가 참조되면 ref_cnt는 1이 된다. y도 x를 참조하니 2가 되고 del x, del y 연산이 수행 될 때 마다 ref_cnt는 1씩 줄어든다. 0이 되면 메모리가 해제된다.

import ctypes
import gc

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

x = object()
x_id = id(x)
print(ref_count(x_id)) # 1
y =  x
print(ref_count(x_id)) # 2
del x
print(ref_count(x_id)) # 1

문제는 순환참조가 발생할 경우 reference counting으로 해결을 못 하는 경우가 생긴다는 것이다. 아래 코드를 보자


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))}')

gc.disable() # GC 끄기

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


aa.ba.ba를 순환참조 하고 있다. a를 None으로 한다면 어디서도 aa.b에 어느 변수도 접근하지 못 하는데 코드를 보면 메모리를 차지하고 있다. 여기서 GC를 켜보면 정상적으로 메모리가 해제된다.

gc.collect() # GC 켜기

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

대략적으로 Cpython의 Garbage collection에 대해 알아봤다. 그런데 reference counting을 보면 동기화가 떠오르지 않는가? 저 연산이 atomic하지 않으면?? 메모리 관리가 전혀 안 되겠는데? 그래서 바로 Cpython에는 GIL이 나온 것! 모든 객체에 Mutex를 걸 수는 없으니 쓰레드 자체에 Mutex를 걸어 버렸다.

동기화란?


참고한 글

profile
내가 배운 것 정리

0개의 댓글