한 번만 실행하려고 하는 경우 간단하게는 다음과 같은 처리를 할 수 있습니다.
int _value = 0; bool _done = false;
void func() { if (_done == false) { _value++; _done = true; } }
당연하지만, 이는 thread-safe하지 않기 때문에 다중 스레드에서 위의 func을 호출하려면 예기치 않은 동작이 발생할 수 있습니다. 위의 상황에서는 _value 값이 2 이상이 나올 수 있는데 이는 다음과 같은 코드로 테스트해 볼 수 있습니다.
using System; using System.Collections.Generic; using System.Threading;
class Program { static int _value = 0; static bool _done = false;
static void Main(string[] args) { int count = 0;
while (true) { _value = 0; _done = false;
List<Thread> list = new List<Thread>(); for (int i = 0; i < 100; i++) { Thread t = new Thread(threadNotSafeFunc); list.Add(t); }
foreach (var item in list) { item.Start(); }
foreach (var item in list) { item.Join(); }
if (_value != 1) { throw new ApplicationException("_value: " + _value); }
Console.WriteLine(count++ + ": Tested - " + _value); } }
private static void threadNotSafeFunc() { Thread.Sleep(1000);
if (_done == false) { _value++; _done = true; } } }
실행해 보면, 여지없이 ApplicationException 예외가 발생합니다. 이를 위해 lock 구문을 써도 되는데, 쓸데없이 참조 형 변수를 하나 둬야 하므로 이럴 땐 Interlocked.CompareExchange를 써 볼 수 있습니다. 원래는 다음과 같이 코딩할 수 있을 텐데,
int _value = 0; bool _done = false;
void threadSafeFunc() { if (Interlocked.CompareExchange(ref _done, true, false) == false) { _value++; } }
아쉽게도 위의 코드를 컴파일하면 다음과 같은 오류가 발생합니다.
Error CS0452 The type 'bool' must be a reference type in order to use it as parameter 'T' in the generic type or method 'Interlocked.CompareExchange<T>(ref T, T, T)
bool 타입을 지원하지 않는 것인데, 사실 좀 이해가 안 됩니다. 어차피 CPU의 워드 타입 내에서 처리할 수 있는 형식이라서 .NET BCL 측에서 제공해줘도 무방할 텐데 굳이 뺀 이유를 모르겠습니다. 이 때문에, bool 타입은 그냥 true를 1로, false를 0으로 해석하는 전통적인 방식에 기인해서 int 타입을 써 다음과 같이 처리해 주시면 됩니다.
int _value = 0; int _intDone = false;
void threadSafeFunc() { if (Interlocked.CompareExchange(ref _intDone, 1, 0) == 0) { _value++; } }
ApplicationException이 발생했던 이전 예제의 threadNotSafeFunc을 threadSafeFunc으로 변경해서 처리하면 예외 없이 잘 실행되는 것을 확인할 수 있습니다.
|