본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev15

with문 없이 파일/락을 다루다가 반복적으로 리소스가 새는 이유

  • #Engineering Note
  • #Python
  • #Resource Management

문제 발생

파일을 열고 처리한 뒤 닫는 코드를 여러 함수에서 반복하다가, 예외가 발생하는 경로에서 close()가 호출되지 않아 파일 핸들이 누적되는 문제가 발생했습니다.

def process(path):
    f = open(path)
    data = f.read()  # 여기서 예외가 나면 f.close()가 실행되지 않음
    result = transform(data)
    f.close()
    return result

원인 분석

open()으로 연 파일은 명시적으로 close()를 호출하기 전까지 운영체제 파일 디스크립터를 점유합니다. 정상 흐름에서만 close()를 호출하는 코드는, 그 사이에 예외가 발생하면 close()를 건너뛰고 함수가 종료됩니다. 이런 패턴이 여러 곳에 반복되면 파일 디스크립터가 서서히 고갈되어 결국 "Too many open files" 같은 오류로 이어집니다.

with문(컨텍스트 매니저)은 정확히 이 문제를 풀기 위한 언어 기능입니다 — 블록을 정상적으로 빠져나가든 예외로 빠져나가든 __exit__이 항상 호출되도록 보장합니다.

해결 방안

  1. 기본적으로 파일, 락, 네트워크 연결 등 "열고 닫아야 하는" 모든 리소스는 with문으로 다룹니다.
def process(path):
    with open(path) as f:
        data = f.read()
        result = transform(data)
    return result  # 예외가 나도 파일은 반드시 닫힘
  1. 직접 만든 리소스(커스텀 연결 객체 등)에 컨텍스트 매니저 프로토콜을 붙이고 싶다면 클래스에 __enter__/__exit__을 구현하거나, 더 간단하게는 contextlib.contextmanager 데코레이터로 제너레이터 함수 하나로 구현할 수 있습니다.
from contextlib import contextmanager

@contextmanager
def acquire_lock(lock):
    lock.acquire()
    try:
        yield
    finally:
        lock.release()  # 예외가 나도 반드시 실행

with acquire_lock(my_lock):
    critical_section()

yield 앞부분이 __enter__, yield 뒷부분(반드시 try/finally로 감싸야 함)이 __exit__ 역할을 합니다.

  1. 여러 리소스를 동시에 열어야 한다면 with open(a) as fa, open(b) as fb:처럼 하나의 with문에 나열하거나, contextlib.ExitStack으로 개수가 동적으로 결정되는 경우까지 처리할 수 있습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.