ThreadLocal<T> 와 AsyncLocal<T> 의 차이점
멀티 스레드 환경에서 각 스레드별로 하나의 공통 데이터에 접근하여 처리 될때 스레드 마다 원자성을 보장하면서
데이터를 다뤄야 하는 상황이 발생할 수 있습니다.
이때 제공 되는것이 Thread Local Storage(TLS) 변수라는 것이 있습니다.
쉽게 말해 tls변수는 스레드별 고유한 데이터를 저장(?)할 수 있는 공간이라고 이해 하면 됩니다. 또한 이 tls변수는(이하 거론되는 ThreadLocal<T> AsyncLocal<T> 동일) 자체적으로 데이터 원자성이 보장되기에 lock없이 접근하여 사용이 가능합니다.
닷넷 C#에서는 이것을 System.ThreadStaticAttribute 어트리뷰트 클래스로 제공 하고 있으며
System.Threading.ThreadLocal<T> 그리고 System.Threading.AsyncLocal<T> 클래스로 제공하고 있고, C++에서는 __declspec(thread)로 사용 가능합니다.
System.Threading.ThreadLocal<T> 같은 경우 System.ThreadStaticAttribute 보다 변수 초기 방법을 제공하고 있어 [ThreadStatic] 어트리뷰트 보다 좀 더 최신 기능을 제공합니다.
이번 포스트는 System.Threading.ThreadLocal<T> 와 System.Threading.AsyncLocal<T> 가 서로 어떤 부분에 차이점이 있는지에 대한 내용 입니다.
자 그럼 어떻게 다른지 살펴보겠습니다.
ThreadLocal<T> AsyncLocal<T> 차이점
위 tls역할을 하는 두 클래스의 큰 차이점은 스레드풀을 사용하는 스레드 환경에서 차이점이 있습니다.
우선 System.Threading.ThreadLocal<T> 은 해당 스레드가 스레드풀에 반환되어도 해당 데이터는 항상 유지 되어 집니다.
private static ThreadLocal<int> _tl = new ThreadLocal<int>();
private static async Task WorkAsync(int i)
{
if(_tl.Value == 0)
{
_tl.Value = i;
Console.WriteLine($"[새로운 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
}
else
{
Console.WriteLine($"[기존 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
}
Console.WriteLine("-----------------------------------------");
}
위 처럼 System.Threading.ThreadLocal<T> 을 사용하는 메서드를 스레드로 호출해 봅니다. 그리고 차이점을 확실히 보기 위해 스레드풀의 스레드 개수 제한을 4개로 설정해 보았습니다.
// ThreadPool의 개수를 4개로 임의 제한
ThreadPool.SetMinThreads(4, 4);
ThreadPool.SetMaxThreads(4, 4);
for (int i = 1; i < 11; i++)
{
await Task.Run(() => WorkAsync(i));
}
스레드를 사용하여 WorkAsync() 메서드를 10번 호출하였고 파라메터로 tls값을 동시에 넘겨 주었습니다.
결과는 어떻게 나올까요?
위 처럼 처음 스레드가 사용되어서 tls값이 설정 되고 해당 스레드가 반환된 이후 다시 같은 스레드가 사용 되었을때
tls값이 처음 설정 된 값으로 유지 되고 있는 걸 확인 할 수 있습니다.
반면,
AsyncLocal<T> 는 어떻게 처리 되는지 위 코드에서 System.Threading.ThreadLocal<T> 부분만 바꿔서 실행 시켜 보겠습니다.
결과를 보니 위 결과와는 다르게 사용된 스레드가 스레드풀에 반환될때는 값이 초기화가 되는 걸 볼 수 있습니다.