C#의 volatile 키워드는 특정 필드가 여러 스레드에 의해 동시에 접근될 수 있음을 컴파일러와 런타임에 알리며, 해당 필드에 대한 읽기 및 쓰기 작업이 최적화되지 않도록 방지합니다. 이를 통해 모든 스레드가 항상 최신 값을 볼 수 있도록 보장하며, 간단한 동기화 요구 사항을 해결하는 데 유용합니다.
volatile의 주요 기능
메모리 가시성 보장
- 다중 스레드 환경에서는 각 스레드가 자체적인 캐시를 사용하기 때문에 한 스레드에서 변경한 값이 다른 스레드에게 즉시 반영되지 않을 수 있습니다.
volatile로 선언된 변수는 항상 메인 메모리에서 직접 값을 읽고, 쓸 때도 즉시 메인 메모리에 반영되어 다른 스레드에서도 최신 상태를 확인할 수 있게 됩니다.
명령어 재정렬 방지
- 컴파일러나 프로세서는 성능 향상을 위해 코드 실행 순서를 조정할 수 있습니다. 그러나 다중 스레드 환경에서는 이러한 재정렬이 예상치 못한 결과를 초래할 수 있습니다.
volatile은 이러한 재정렬을 금지함으로써 프로그램의 논리적 순서를 유지합니다.
간단한 동기화에 적합
- 하나의 플래그 변수(
_shouldStop)처럼 단순한 상태 전달에 효과적입니다. - 복잡한 상태 관리나 여러 리소스 간의 일관성을 요구하는 경우에는
lock,Monitor, 또는CancellationToken같은 더 강력한 동기화 도구를 사용해야 합니다.
예제: volatile을 이용한 기본적인 스레드 제어
다음 예제는 백그라운드 스레드의 실행 여부를 제어하기 위해 volatile을 사용하는 방법을 보여줍니다.
using System;
using System.Threading;
class Worker
{
private static volatile bool _isActive = true;
static void Main()
{
Thread workerThread = new Thread(RunTask);
workerThread.Start();
Thread.Sleep(2000);
Console.WriteLine("작업 종료 요청 중...");
_isActive = false;
workerThread.Join();
Console.WriteLine("작업이 종료되었습니다.");
}
static void RunTask()
{
while (_isActive)
{
Console.WriteLine("작업 진행 중...");
Thread.Sleep(500);
}
Console.WriteLine("작업 스레드 종료됨.");
}
}
출력 예시:
작업 진행 중...
작업 진행 중...
작업 진행 중...
작업 진행 중...
작업 종료 요청 중...
작업 스레드 종료됨.
작업이 종료되었습니다.
volatile의 한계점
- 단일 필드만 지원: 여러 필드 간의 일관성이 필요한 작업에는 부적합합니다.
- 원자성 보장하지 않음: 예를 들어
i++와 같은 연산은 내부적으로 여러 단계를 거치므로,volatile만으로는 스레드 안전하지 않습니다. - 복합 조건문 비지원:
if(x == 1 && y == 2)와 같은 복합 조건 검사는volatile만으로는 원자적으로 처리할 수 없습니다.
권장되는 사용 시나리오
- 상태 통보용 플래그: 스레드 종료 신호와 같이 단순한 상태 변화만을 전달할 때 유용합니다.
- 성능 고려형 동기화: 락 오버헤드를 피하고 싶으며, 동기화 범위가 매우 좁은 경우에 적절합니다.
사용하지 말아야 할 상황
- 복잡한 자료 구조 동기화: 여러 변수 간의 일관성을 유지해야 하는 경우
lock이나ConcurrentQueue<T>등을 사용해야 합니다. - 원자적 연산 필요 시: 카운터 증감과 같은 작업은
Interlocked.Increment()와 같은 메서드를 사용해야 정확하게 처리됩니다.
volatile은 멀티스레딩 환경에서 간단한 상태 공유 문제를 해결하는 경량 솔루션이지만, 복잡한 동시성 제어에는 적합하지 않습니다. 따라서 적절한 상황에서만 사용하고, 그 외에는 강력한 동기화 기법을 적용하는 것이 바람직합니다.