자원이 여러 개 있고 스레드들이 자원에 접근하여 사용을 하는 경우가 있다고 생각해보겠습니다.
자원마다 Lock을 걸게 되면 자원에 대한 원자성은 보장되지만,
하나의 자원에 대해 여러 스레드의 접근이 이뤄지면 대기가 길어져 처리가 늦춰지는 문제가 발생합니다.
그리고 이런 경우 대기가 길어져 멀티 스레드의 장점을 살리지 못하고
싱글 스레드보다도 효율적이지 못한 방식이 되어버립니다.
이런 상황에서 사용할 수 있는 것이 바로 Thread Local Storage 즉, TLS입니다.
동적할당되는 데이터들을 위한 Heap 영역이나 전역 변수와 같은 공용 변수를 위한 Data 영역같은 경우
모든 스레드들이 같이 사용하는 영역입니다.
그에 반해 지역변수같은 데이터를 위한 Stack 영역의 경우 스레드들이 각각 갖고 있습니다.
TLS는 스레드들이 개별적으로 갖는 Data 영역으로서
Data 영역의 데이터를 TLS에 담아서 스레드가 지역적으로 처리할 수 있게 해줍니다.
TLS는 다음과 같이 사용할 수 있습니다.
기존의 전역 변수에서
static 자료형 변수명;
다음과 같이 변경하여 사용합니다.
static ThreadLocal<자료형> 변수명;
이제 TLS를 사용하여 전역변수를 스레드별로 어떻게 처리하는지 알아보겠습니다.
1) 첫 번째 예제
class Program
{
static ThreadLocal<string> ThreadName = new ThreadLocal<string>();
static void SetNameOfThread()
{
// TLS에 저장된 변수의 데이터는 Value 프로퍼티를 통해 접근해야 함
ThreadName.Value = $"This Thread`s name is {Thread.CurrentThread.ManagedThreadId}";
// ThreadName을 점유하고 있는 스레드를 1000초 동안 중지시킴
Thread.Sleep(1000);
// 다른 스레드가 ThreadName을 점유한 이후 데이터가 변경되었는지 확인
Console.WriteLine(ThreadName.Value);
}
static void Main(string[] args)
{
// Parallel의 Invoke 함수를 통해 ThreadPool의 스레드들을 인자로 넣어준 함수의 수만큼 생성한 후
// Start()와 Wait()를 수행함
Parallel.Invoke(SetNameOfThread, SetNameOfThread, SetNameOfThread, SetNameOfThread, SetNameOfThread);
}
}
위와 같이 코드를 구성하여 동작시켜보겠습니다.
실행 결과는 다음과 같습니다.
의도한 대로 ThreadName은 개별 스레드에 의해 조작된 것을 확인할 수 있습니다.
그러나 위의 코드의 문제점은 개별 스레드마다 매번 새로운 ThreadName.Value를 생성하고 있다는 것입니다.
기존에 생성된 데이터를 그대로 사용하면 쓸모없는 메모리의 낭비를 막을 수 있습니다.
따라서 다음과 같이 개선해보겠습니다.
2) 개선된 예제
class Program
{
// Data 영역이 아닌 TLS 영역에 저장
// TLS에 저장되었으므로 어떤 스레드가 새로 쓴다고 해도 다른 스레드에는 영향이 없음
static ThreadLocal<string> ThreadName = new ThreadLocal<string>(()=> { return $"This Thread`s name is {Thread.CurrentThread.ManagedThreadId}"; });
static void SetNameOfThread()
{
bool CheckThreadNameIsValid = ThreadName.IsValueCreated;
if (CheckThreadNameIsValid)
Console.WriteLine(ThreadName.Value + " Repeated");
else
Console.WriteLine(ThreadName.Value);
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(3, 3);
// Parallel의 Invoke 함수를 통해 ThreadPool의 스레드들을 인자로 넣어준 함수의 수만큼 생성한 후
// Start()와 Wait()를 수행함
Parallel.Invoke(SetNameOfThread, SetNameOfThread, SetNameOfThread, SetNameOfThread, SetNameOfThread);
}
}
기존의 SetNameOfThread 함수와는 다르게
우선 ThreadName의 Value가 유효한지 확인합니다.
만약 유효하다면, 그대로 사용하고 Repeated 수식자를 붙여 출력합니다.
아니라면 새로운 ThreadNamed을 생성하여 사용합니다.
이런 처리를 통해 메모리의 낭비를 막을 수 있습니다.
실제 실행 결과는 다음과 같습니다.
최대 스레드의 개수를 3개로 제한하였기에 최대 3개의 ThreadName이 생성되고
새로운 Thread가 3개까지 생성된 이후로는 기존의 ThreadName을 그대로 사용하는 것을 확인할 수 있습니다.
'C#' 카테고리의 다른 글
소켓 프로그래밍 기초 (0) | 2024.12.16 |
---|---|
네트워크 기초 + 통신 모델 (0) | 2024.12.11 |
ReaderWriterLock 구현 (0) | 2024.12.09 |
Lock 구현 훑어내리기 + ReaderWriterLock 기초 (0) | 2024.12.04 |
Event를 통한 Lock 구현 (0) | 2024.12.04 |