본문 바로가기
C#/쓰레드

원자적 작업(Atomic Operation): 동시성 문제 해결의 핵심 개념

by 공부봇 2025. 1. 16.
반응형

원자적 작업(Atomic Operation)란?

**원자적 작업(Atomic Operation)**은 더 이상 나눌 수 없는 단일 작업 단위를 의미합니다. 즉, 어떤 작업이 원자적이라고 할 때, 이는 해당 작업이 중단되거나 다른 작업과 간섭받지 않고 한 번에 완전히 실행된다는 것을 뜻합니다.


원자적 작업의 특성

  1. 불가분성 (Indivisibility):
    • 작업이 시작되면 완료될 때까지 외부의 개입 없이 수행됩니다.
    • 작업이 중간에 중단될 가능성이 없으며, 다른 스레드가 해당 작업에 간섭할 수 없습니다.
  2. 중단 방지 (Interrupt-Proof):
    • 다른 스레드가 해당 변수나 자원에 접근하려 하더라도 작업이 완료된 이후에만 접근이 가능합니다.
  3. 일관성 (Consistency):
    • 원자적 작업은 일관된 상태를 보장합니다. 작업이 성공하면 그 결과가 완전히 반영되고, 실패하면 아무런 변화도 없습니다.

원자적 작업의 예

비원자적 작업의 문제

아래는 원자적이지 않은 작업의 예입니다.

int counter = 0;

void Increment()
{
    counter++;
}

이 작업은 사실 세 가지 단계로 나뉘어집니다:

  1. counter의 현재 값을 읽음.
  2. 읽은 값에 1을 더함.
  3. 결과를 counter에 씀.

이 세 단계 중 다른 스레드가 동일한 변수(counter)를 읽거나 수정하면 경합 조건이 발생할 수 있습니다.


원자적 작업의 구현

위 작업을 Interlocked를 사용하여 원자적으로 처리하면, 하나의 단일 작업으로 처리됩니다.

void Increment()
{
    Interlocked.Increment(ref counter);
}

Interlocked.Increment(ref counter)는 내부적으로 하드웨어 명령어를 사용하여 값을 한 번에 증가시킵니다. 이 작업은 다음을 보장합니다:

  • 값의 읽기, 수정, 쓰기 과정이 단일 작업으로 묶임.
  • 작업이 중단되거나 간섭받지 않음.

원자적 작업의 예시와 활용

  1. 증가와 감소:
    • 간단한 카운터에서 주로 사용됩니다.
    int counter = 0;
    
    // 원자적으로 증가
    Interlocked.Increment(ref counter);
    
    // 원자적으로 감소
    Interlocked.Decrement(ref counter);
    
  2. 값 교환:
    • 특정 변수의 값을 새로운 값으로 변경하면서, 이전 값을 반환합니다.
    int value = 42;
    
    // value를 100으로 변경하고 이전 값을 oldValue에 저장
    int oldValue = Interlocked.Exchange(ref value, 100);
    
    Console.WriteLine($"Old Value: {oldValue}, New Value: {value}");
    
  3. 조건부 값 변경:
    • 변수의 값이 특정 값(comparand)일 때만 새로운 값으로 설정합니다.
    int currentValue = 10;
    int newValue = 20;
    
    // currentValue가 10이면 20으로 변경
    int originalValue = Interlocked.CompareExchange(ref currentValue, newValue, 10);
    
    Console.WriteLine($"Original Value: {originalValue}, Current Value: {currentValue}");
    
  4. 값 더하기:
    • 특정 값을 기존 값에 더합니다.
    int total = 0;
    
    // total에 5를 원자적으로 더함
    Interlocked.Add(ref total, 5);
    
    Console.WriteLine($"Total: {total}");
    

원자적 작업의 실제 사용 사례

  1. 스레드 안전한 카운터:
    • 다중 스레드 환경에서 동기화를 사용하지 않고 값을 안전하게 증감합니다.
    int counter = 0;
    
    void IncrementCounter()
    {
        Interlocked.Increment(ref counter);
    }
    
  2. 플래그 설정 및 교환:
    • 스레드 간의 플래그를 설정하거나 교환할 때 사용됩니다.
    int isProcessing = 0;
    
    void StartProcessing()
    {
        if (Interlocked.Exchange(ref isProcessing, 1) == 0)
        {
            Console.WriteLine("Processing started.");
        }
    }
    
  3. 락 없이 공유 상태 관리:
    • 락을 사용하지 않고 간단한 공유 상태를 관리할 때 사용합니다.
    int sharedValue = 0;
    
    void UpdateSharedValue()
    {
        Interlocked.Add(ref sharedValue, 10);
    }
    

원자적 작업이 중요한 이유

  • 다중 스레드 환경에서 작업이 중단 없이 실행되므로 경합 조건을 방지할 수 있습니다.
  • lock 또는 Monitor와 비교하여 성능이 더 우수하며, 간단한 값 조작에는 적합합니다.
  • 프로세스나 스레드 간의 일관된 상태를 보장합니다.

한계점:

  • 단일 변수에 대한 작업만 원자적으로 보장되며, 여러 변수나 복잡한 작업에는 적합하지 않습니다. 이 경우에는 lock 또는 Monitor 같은 동기화 메커니즘을 사용해야 합니다.

원자적 작업은 경량 동기화를 제공하는 강력한 도구로, 다중 스레드 프로그래밍에서 효율적이고 안전한 방법으로 사용됩니다.

반응형