Menu
Coddy logo textTech

스레드 세이프 싱글톤

Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 70개 중 52번째.

싱글톤 패턴(Singleton pattern)은 애플리케이션 전체에서 클래스의 인스턴스가 단 하나만 존재하도록 보장하며, 이에 대한 전역적인 접근 지점을 제공합니다. 이는 설정 정보, 로깅 서비스 또는 데이터베이스 연결과 같은 공유 리소스를 관리하는 데 유용합니다.

기본적인 싱글톤(Singleton)은 외부 인스턴스화를 방지하기 위해 private 생성자를 사용하고, 접근을 제공하기 위해 static 속성을 사용합니다:

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // Private constructor
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

lock 문은 스레드 안전성(thread safety)을 보장합니다. 여러 스레드가 동시에 Instance에 접근하려고 하면, 한 번에 하나의 스레드만 임계 구역(critical section)에 진입할 수 있습니다. 이것이 없으면, 두 스레드가 모두 _instance를 null로 보고 별도의 인스턴스를 생성할 수 있습니다.

C#은 정적 초기화를 사용하여 더 간단한 스레드 안전(thread-safe) 방식을 제공합니다:

public class Logger
{
    private static readonly Logger _instance = new Logger();
    
    private Logger() { }
    
    public static Logger Instance => _instance;
    
    public void Log(string message) => Console.WriteLine(message);
}

이 버전은 CLR이 정적 필드 초기화가 단 한 번만 발생하도록 보장하기 때문에 스레드 안전(thread-safe)합니다. 두 방식 모두 항상 동일한 인스턴스를 얻을 수 있도록 보장합니다:

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");
challenge icon

챌린지

쉬움

싱글톤 패턴을 사용하여 스레드로부터 안전한 설정 관리자(configuration manager)를 만들어 보겠습니다. 실제 애플리케이션에서 설정 정보는 프로그램 전체에서 접근 가능해야 하며, 단 하나의 인스턴스만 존재하도록 보장해야 합니다. 이는 싱글톤 패턴을 적용하기에 완벽한 사례입니다.

코드는 두 개의 파일로 구성됩니다:

  • ConfigManager.cs: Configuration 네임스페이스 안에 싱글톤 패턴을 구현하는 ConfigManager 클래스를 생성합니다. 설정 관리자는 다음 사항을 포함해야 합니다:
    • 단일 인스턴스를 유지하기 위한 private static 필드
    • 외부에서 인스턴스화를 방지하기 위한 private 생성자
    • 단일 인스턴스를 반환하는 static Instance 속성 (스레드 안전성을 위해 더 간단한 정적 초기화 방식을 사용하세요)
    • "MyApp"으로 초기화된 private 문자열 필드 _appName
    • 앱 이름을 업데이트하는 SetAppName(string name) 메서드
    • 현재 앱 이름을 반환하는 GetAppName() 메서드
  • Program.cs: 메인 파일에서 인스턴스에 여러 번 접근하고 모든 참조에서 변경 사항이 유지되는 것을 보여줌으로써 싱글톤이 올바르게 작동함을 증명합니다.

하나의 입력을 받게 됩니다:

  • 설정할 새로운 애플리케이션 이름 (예: ProductionServer)

프로그램의 동작 순서:

  1. ConfigManager 인스턴스를 가져와 기본 앱 이름을 출력합니다.
  2. 인스턴스를 다시 가져와(다른 변수에 저장) 입력받은 새로운 앱 이름을 설정하는 데 사용합니다.
  3. 인스턴스를 세 번째로 가져와 앱 이름을 출력하여 모든 참조가 동일한 데이터를 공유함을 증명합니다.

예를 들어, 입력이 ProductionServer인 경우 출력은 다음과 같아야 합니다:

MyApp
ProductionServer

이는 싱글톤의 핵심적인 이점을 보여줍니다. Instance에 얼마나 많이 접근하든 관계없이 항상 상태를 공유하는 동일한 객체를 얻게 됩니다. 정적 초기화 방식은 CLR이 여러 스레드가 동시에 접근하더라도 정적 필드가 단 한 번만 초기화되도록 보장하기 때문에 스레드 안전성을 보장합니다.

치트 시트

싱글톤 패턴(Singleton pattern)은 애플리케이션 전체에서 클래스의 인스턴스가 단 하나만 존재하도록 보장하며, 이에 대한 전역적인 접근 지점을 제공합니다.

스레드 안전성(thread safety)을 위해 명시적 잠금(explicit locking)을 사용하는 기본 싱글톤:

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // 프라이빗 생성자
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

lock 문은 한 번에 하나의 스레드만 임계 구역(critical section)에 진입할 수 있도록 허용하여 스레드 안전성을 보장합니다.

정적 초기화(static initialization)를 사용한 더 간단한 스레드 안전 싱글톤:

public class Logger
{
    private static readonly Logger _instance = new Logger();
    
    private Logger() { }
    
    public static Logger Instance => _instance;
    
    public void Log(string message) => Console.WriteLine(message);
}

CLR이 정적 필드 초기화가 단 한 번만 발생하도록 보장하기 때문에 이 방식은 스레드로부터 안전합니다.

사용법 - 항상 동일한 인스턴스를 반환합니다:

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");

직접 해보기

using System;
using Configuration;

class Program
{
    public static void Main(string[] args)
    {
        // 입력 읽기 - 새 애플리케이션 이름
        string newAppName = Console.ReadLine();
        
        // TODO: ConfigManager 인스턴스를 가져와서 기본 애플리케이션 이름을 출력하세요
        
        // TODO: 인스턴스를 다시 가져와서(다른 변수에 저장) 새 애플리케이션 이름을 설정하는 데 사용하세요
        
        // TODO: 인스턴스를 세 번째로 가져와서 애플리케이션 이름을 출력하여 모든 참조가 동일한 데이터를 공유하는지 확인하세요
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨