volatile 키워드
volatile 키워드 (C# 참조)
여러 스레드가 동시에 실행되면서 같은 필드를 바꿀 수 있는 상황을 만나면, volatile 키워드로 그 필드를 표시해 둘 수 있어요. 성능을 위해 컴파일러와 런타임, 심지어 하드웨어까지 메모리 위치에 대한 읽기·쓰기의 순서를 바꿀 수 있는데, 필드를 volatile로 선언하면 이런 류의 최적화에서 제외됩니다. 다만 주의할 점이 있어요. 실행 중인 모든 스레드가 바라보는 volatile 쓰기 순서에 단일 전체 순서(single total ordering)가 보장되지는 않습니다. 자세한 내용은 xref:System.Threading.Volatile 클래스를 참고하세요.
본문
먼저 짚고 넘어갈게요. volatile 키워드는 다중 스레드 프로그래밍에서 자주 오해되고 잘못 쓰이기도 해요. 대부분의 시나리오에서는 volatile 대신 더 안전하고 신뢰할 수 있는 대안을 쓰는 편이 좋습니다. 최신 .NET은 xref:System.Threading.Interlocked 클래스, lock 문, 혹은 더 상위 수준의 동기화 기본 요소 같은 더 나은 동시성 도구를 제공해요. 이것들은 volatile보다 더 명확한 의미와 더 강한 보장을 줍니다. volatile은 그 제약을 충분히 이해하고 적절한 해법임을 직접 검증한, 드물고 고급스러운 시나리오에서만 고려하는 걸 권장할게요.
멀티프로세서 시스템에서는 volatile 읽기 연산이 다른 프로세서가 그 메모리 위치에 쓴 최신 값을 얻는다는 보장이 없어요. 마찬가지로 volatile 쓰기 연산도 쓴 값이 즉시 다른 프로세서에 보인다는 보장이 없습니다.
volatile 키워드는 다음 타입의 필드에만 적용할 수 있어요.
- 참조 타입(Reference types)
- 포인터 타입(unsafe 컨텍스트에서). 포인터 자체는 volatile일 수 있지만 포인터가 가리키는 객체는 그럴 수 없어요. 다시 말해 "volatile에 대한 포인터(pointer to volatile)"는 선언할 수 없습니다.
sbyte,byte,short,ushort,int,uint,char,float,bool같은 단순 타입(Simple types)- 다음 기본 타입 중 하나를 갖는
enum타입:byte,sbyte,short,ushort,int,uint - 참조 타입으로 알려진 제네릭 타입 매개변수
- xref:System.IntPtr 및 xref:System.UIntPtr
double과 long을 포함한 다른 타입은 volatile로 표시할 수 없어요. 그 타입의 필드에 대한 읽기·쓰기가 원자적(atomic)임을 보장할 수 없기 때문입니다. 이런 타입의 필드에 대한 다중 스레드 접근을 보호하려면 xref:System.Threading.Interlocked 클래스의 멤버를 쓰거나 lock 문으로 접근을 보호하세요.
지원되는 타입이라도 대부분의 다중 스레드 시나리오에서는 volatile 대신 xref:System.Threading.Interlocked 연산, lock 문, 혹은 다른 동기화 기본 요소를 쓰는 게 낫습니다. 이런 대안은 미묘한 동시성 버그에 덜 노출돼요.
volatile 키워드는 class나 struct의 필드에만 적용할 수 있어요. 지역 변수는 volatile로 선언할 수 없습니다.
volatile의 대안들
대부분의 경우 volatile 대신 다음 중 하나의 더 안전한 대안을 사용하세요.
- xref:System.Threading.Interlocked 연산: 숫자 타입과 참조 할당에 대한 원자적 연산을 제공해요. 일반적으로 더 빠르고
volatile보다 더 강한 보장을 줍니다. lock문: 상호 배제와 메모리 장벽을 제공해요. 더 큰 임계 구역을 보호할 때 사용합니다.- xref:System.Threading.Volatile 클래스:
volatile키워드보다 더 명확한 의미로 명시적인 volatile 읽기·쓰기 연산을 제공해요. - 더 상위 수준의 동기화 기본 요소: xref:System.Threading.ReaderWriterLockSlim, xref:System.Threading.Semaphore 같은 것, 혹은 xref:System.Collections.Concurrent의 동시성 컬렉션을 말합니다.
volatile 키워드는 할당(assignment) 외의 연산에는 원자성을 제공하지 않아요. 경쟁 조건(race condition)을 막지도 않고, 다른 메모리 연산에 대한 순서 보장도 제공하지 않습니다. 이런 제약 때문에 대부분의 동시성 시나리오에는 적합하지 않아요.
아래 예제는 public 필드 변수를 volatile로 선언하는 방법을 보여줘요.
class VolatileTest
{
public volatile int sharedStorage;
public void Test(int i)
{
sharedStorage = i;
}
}
다음 예제는 보조(worker) 스레드를 만들고, 이를 주 스레드와 병렬로 처리하는 데 어떻게 사용하는지 보여줍니다. 멀티스레딩에 대한 자세한 내용은 관리되는 스레딩을 참고하세요.
public class Worker
{
// This method is called when the thread is started.
public void DoWork()
{
bool work = false;
while (!_shouldStop)
{
work = !work; // simulate some work
}
Console.WriteLine("Worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Keyword volatile is used as a hint to the compiler that this data
// member is accessed by multiple threads.
private volatile bool _shouldStop;
}
public class WorkerThreadExample
{
public static void Main()
{
// Create the worker thread object. This does not start the thread.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
// Start the worker thread.
workerThread.Start();
Console.WriteLine("Main thread: starting worker thread...");
// Loop until the worker thread activates.
while (!workerThread.IsAlive)
;
// Put the main thread to sleep for 500 milliseconds to
// allow the worker thread to do some work.
Thread.Sleep(500);
// Request that the worker thread stop itself.
workerObject.RequestStop();
// Use the Thread.Join method to block the current thread
// until the object's thread terminates.
workerThread.Join();
Console.WriteLine("Main thread: worker thread has terminated.");
}
// Sample output:
// Main thread: starting worker thread...
// Worker thread: terminating gracefully.
// Main thread: worker thread has terminated.
}
이 예제에서 _shouldStop 선언에 volatile 한정자를 붙이면 실행할 때마다 항상 같은 결과를 얻을 수 있어요(위 코드 발췌와 비슷한 결과죠). 그런데 _shouldStop 멤버에 이 한정자가 없으면 동작이 예측 불가능해집니다. DoWork 메서드가 멤버 접근을 최적화하다 보면 오래된(stale) 데이터를 읽게 될 수 있어요. 다중 스레드 프로그래밍의 특성상 낡은 읽기가 몇 번이나 일어날지는 예측할 수 없고, 프로그램을 실행할 때마다 결과가 조금씩 달라집니다.
C# 언어 사양
더 자세한 내용은 C# 언어 사양을 참고하세요. 언어 사양은 C# 구문과 사용법에 대한 권위 있는 출처입니다.