프로그래밍/C#

[C#] Singleton 패턴 잘못 쓰면 발생하는 문제 – 메모리 누수 & DI 문제

큐레이트 2025. 3. 24. 08:25

[C#] Singleton 패턴 잘못 쓰면 발생하는 문제 – 메모리 누수 & DI 문제

C#에서 Singleton 패턴은 전역 인스턴스를 하나만 유지한다는 점에서 자주 사용됩니다. 하지만 잘못된 싱글톤 사용은 메모리 누수, 테스트 어려움, 의존성 주입(DI) 충돌 등의 문제를 발생시킬 수 있습니다.

이 글에서는 Singleton 패턴의 기본 구조와 함께 실무에서 문제가 되는 사용 예, 그리고 올바른 구현 방법을 소개하겠습니다.


✅ Singleton 패턴 기본 구조


// 기본 싱글톤 구현 (Lazy + Thread-safe)
public sealed class MyService
{
    private static readonly Lazy<MyService> instance =
        new Lazy<MyService>(() => new MyService());

    public static MyService Instance => instance.Value;

    private MyService()
    {
        // 생성자
    }

    public void DoSomething()
    {
        Console.WriteLine("작업 실행");
    }
}

이 구조는 실무에서 가장 많이 쓰이는 Lazy + Thread-safe 패턴입니다.
하지만 아래와 같은 문제들을 유발할 수 있습니다.


⚠️ 문제 1 – 테스트 코드 작성 어려움

싱글톤 인스턴스는 전역 상태를 가지기 때문에, 단위 테스트에서 격리(Isolation)된 테스트 작성이 어렵습니다.
Mocking이 어렵고, 테스트 간 상태 공유로 인해 예기치 않은 결과가 발생할 수 있습니다.


// 다음 테스트는 상태 공유 문제를 일으킬 수 있음
MyService.Instance.DoSomething(); // 이전 테스트의 상태가 남아있음

⚠️ 문제 2 – DI(Dependency Injection)와 충돌

최근에는 의존성 주입(Dependency Injection)을 활용하는 경우가 많은데, 이 때 Singleton 패턴은 DI 컨테이너가 객체 생명주기를 관리하는 방식과 충돌할 수 있습니다.


// DI Container에서 관리되는 객체 예시
services.AddSingleton<IMyService, MyService>();

DI 컨테이너를 사용한다면 굳이 수동으로 Singleton 패턴을 구현할 필요 없이 컨테이너에게 Singleton 생명주기를 맡기는 게 더 안전하고 유연합니다.


⚠️ 문제 3 – 메모리 누수

GC(가비지 컬렉터)는 더 이상 참조되지 않는 객체를 정리하지만, Singleton은 앱 종료 시까지 메모리에 유지되기 때문에 불필요한 리소스가 계속 메모리에 남아 메모리 누수처럼 보일 수 있습니다.


// 싱글톤 내부에서 IDisposable 리소스를 직접 관리하면 누수 위험
private SqlConnection conn = new SqlConnection(...);

→ 이럴 땐 IDisposable을 구현하지 말고, 외부에서 의존성 주입 받는 구조로 개선해야 합니다.


✅ 해결 방법 – DI와 함께 쓰는 Singleton

.NET Core 이상에서는 Microsoft.Extensions.DependencyInjection을 통해 싱글톤 생명주기를 컨트롤 할 수 있습니다.


// Startup.cs 또는 Program.cs에서 등록
services.AddSingleton<IMyService, MyService>();

// 생성자 주입으로 사용
public class MyController
{
    private readonly IMyService myService;

    public MyController(IMyService myService)
    {
        this.myService = myService;
    }
}

이처럼 DI Container를 활용한 Singleton 등록 방식이 테스트 코드 작성, 생명주기 관리, 메모리 안정성 측면에서 훨씬 유리합니다.


🧠 마무리 요약

  • 전통적인 Singleton 패턴은 간단하지만 테스트/확장/DI 측면에서 단점이 많음
  • 의존성 주입을 활용해 Singleton을 안전하게 구현하는 것이 권장됨
  • 싱글톤 내부에 리소스를 직접 소유하지 말고, 외부에서 주입받는 구조가 이상적

실무에서는 항상 유지보수성과 테스트 가능성을 고려한 구조 설계가 중요합니다.
이 글이 도움이 되셨다면 공감/댓글 부탁드려요 😊

반응형