본문 바로가기
C#

[C#] Thread

by DANEW 2023. 11. 1.

Thread 클래스

 

Thread 클래스 (System.Threading)

스레드를 만들고 제어하며, 해당 속성을 설정하고, 상태를 가져옵니다.

learn.microsoft.com

스래드 생성

- Thread 객체를 인스턴스화해서 Start 메서드 호출

- 인자: ThreadStart 대리자 하나, 스레드 시동 메서드 - 매개변수 없는 메서드를 가리켜야 함

public static void Main(string[] arg)
{
    Thread t = new Thread(WriteY);
    t.Start();
 
    for (int i = 0; i < 1000; i++)
    {
        Console.Write("x");
    }
}
 
static void WriteY()
{
    for (int i = 0; i < 1000; i++)
    {
        Console.Write("y");
    }
}

- 단일 코어: 동시성을 흉내내기 위해 시간 조각들을 스레드에 할당(Windows에서 20ms 정도)
- 다중 코어: 실제로 병렬로 실행
- Console의 동시적 요청 처리 방식 때문에 x, y가 섞여서 나옴
- 생성자에 전달한 대리자의 실행이 끝나면 종료됨
- 종료된 스레드를 다시 실행하지는 못 함
- IsAlive: 실행중이면 true
- Name: 디버그를 위한 스레드 이름,
              VS에서 스레드 이름을 스레드 창과 디버그 위치 도구에 표시해 줌, 한번만 설정 가능
- Thread.CurrentThread: 해당 호출이 실행되고 있는 스레드를 반환

 

Join 메서드

- 현재 스레드에서 다른 스레드의 실행이 끝나길 기다림(현 스래드 실행 차단)
- Join에 만료 시간을 ms 단위의 int나 TimeSpan 객체로 지정 가능 - 스래드 종료시 true, 시간 만료시 false 반환

public static void Main(string[] arg)
{
    Thread t = new Thread(WriteY);
    t.Start();
    t.Join();
 
    Console.WriteLine("스레드 t가 실행을 완료했습니다.");
}
 
static void WriteY()
{
    for (int i = 0; i < 1000; i++)
    {
        Console.Write("y");
    }
    Console.WriteLine();
}

 

Sleep 메서드- 현재 스레드를 일정 시간 재움(현 스래드 실행 차단)
- Thread.Sleep(0)은 스레드의 현재 시간 조각을 즉시 포기하고 다른 스레드에게 양보
 * Thread.Yield()도 같은 효과를 내지만, 같은 프로세서에서 실행되는 스레드들에게만 양보
 * 실무에서, 코드에 어딘가에 Thread.Yield()를 추가했는데
   프로그램이 오작동 한다면 프로그램에 버그가 있는 것이 거의 확실

 

ThreadState 열거형

namespace System.Threading
{
    public enum ThreadState
    {
        Running = 0,
        StopRequested = 1,
        SuspendRequested = 2,
        Background = 4,
        Unstarted = 8,
        Stopped = 16,
        WaitSleepJoin = 32,
        Suspended = 64,
        AbortRequested = 128,
        Aborted = 256
    }
}

- 주어진 스레드가 차단되어있는지는 ThreadState 속성으로 알 수 있음
- 진단용으로는 유용하지만, 동기화에 사용하기는 적합하지 않음
 * 읽어서 상태를 알아내는 시점과 그 상태에 근거에서 수행하는 시점 사이에서 스레드의 상태가 변할 수 있음

public static void Main(string[] arg)
{
    Thread t = new Thread(Test);
    t.Start();
 
    Thread.Sleep(100);
 
    bool isBlocked = (t.ThreadState & ThreadState.WaitSleepJoin) != 0;
    Console.WriteLine(isBlocked);
}
 
static void Test()
{
    Thread.Sleep(1000);
}

스레드에 자료 전달

- 스레드 시동 메서드에 인수들을 전달해야 할 때

1. 해당 인수들로 메서드를 호출하는 람다식을 사용

public static void Main(string[] arg)
{
    string message = "전송될 스트링";
    bool mb = false;
 
    List<Thread> threadList = new List<Thread>();
 
    // 람다식
    threadList.Add(new Thread(() => PrintMessage(message)));
 
    // 여러개의 인자, call by reference도 문제 없다
    threadList.Add(new Thread(() => PrintMessageWithBool(message, out mb)));
 
    // 다중문 람다
    threadList.Add(new Thread(() =>
    {
        Console.WriteLine(message);
    }));
 
 
    // Task를 쓰면 더 편할텐데...
    foreach (var item in threadList)
    {
        item.Start();
    }
 
    foreach (var item in threadList)
    {
        item.Join();
    }
 
    Console.WriteLine(mb);
}
 
static void PrintMessage(string message)
{
    Console.WriteLine(message);
}
 
static void PrintMessageWithBool(string message, out bool mb)
{
    Console.WriteLine(message);
    mb = true;
}

2. C# 3.0 이전의 람다식이 없던 시절의 방식

public static void Main(string[] arg)
{
    string message = "전송될 스트링";
 
    // public delegate void ParameterizedThreadStart(object obj);
    // 위 대리자를 이용한 생성자
    Thread t = new Thread(PrintMessage);
 
    t.Start(message);
}
 
static void PrintMessage(object message)
{
    // object 타입이기 때문에 필요하다면 원래 형태로 케스팅을 해준다
    Console.WriteLine(message);
}

- 인수가 하나 뿐, 2개 이상의 인수는 사용 불가
- 인수의 형식이 object 타입이라서 대부분 원본 형식으로 다시 캐스팅을 해줘야 함

 

전경 스레드 대 배경 스레드

- IsBackground 속성으로 조회 가능
- 전경 스레드(Foreground Thread): 프로그램이 명시적으로 생성한 스레드,
                                                         전경 스레드가 하나라도 살아있으면 응용 프로그램은 종료되지 않음
- 배경 스레드(Background Thread): 전경이 모두 종료되면 배경은 강제로 종료됨
- 배경 스레드가 강제로 종료되면 배경 스레드의 fianlly 블록이 실행되지 않음
 * 임시 파일 삭제 같은 어떤 정리 작업을 finally에서 하거나 using문을 수행 할 경우
   스레드 합류(Join)이나 신호 대기를 이용하여 배경 스레드의 종료를 기다려줘야 함
 * 기다리지 않고 강제 종료하면 프로그램이 제대로 종료되지 않아 작업 관리자로 직접 꺼줘야 함
- 전경 스레드의 경우엔 이런 처리는 필요 없지만 스레드가 종료되지 않는 버그를 피하는 것은 여전히 중요스레드 우선순위

namespace System.Threading
{
    public enum ThreadPriority
    {
        Lowest = 0,
        BelowNormal = 1,
        Normal = 2,
        AboveNormal = 3,
        Highest = 4
    }
}

- Priority 속성 - ThreadPriority 열거형을 값으로 씀
- 스레드에 할당되는 실행 시간(운영체제의 다른 활성 스레드에 상대적으로)을 결정
- 스레드의 우선순위를 다른 프로세스의 스레드들보다 높이려면 
  Process 클래스를 이용하여 프로세스 자체의 우선순위도 높혀야 함

신호 대기

- 어떤 스레드가 신호할 때까지 한 스레드를 대기 상태로 두어야 할 때
- ManualResetEvent
 * 가장 간단한 신호 대기 수단
 * WaitOne: 대기 상태로 전환
 * Set: 신호 전달
 * Reset: 신호를 닫힌 상태로 전환

var signal = new ManualResetEvent(false);
 
new Thread(() =>
{
    Console.WriteLine("Waiting for Signal...");
    signal.WaitOne();
    signal.Dispose();
    Console.WriteLine("Signal Recevied!");
}).Start();
 
Thread.Sleep(2000);
signal.Set();

스레드 풀

 

- 스레드를 하나 띄우면 지역 변수 스택 등을 마련하는데 수백 마이크로초가 소비됨
- 스래드 풀(Thread Pool)은 재활용 가능한 스레드들을 미리 만들어서 풀에 담아 둠으로서 지연을 제거
- 효율적인 병렬 프로그래밍 및 세밀한 동시성을 구현하는데 필수
- 풀에서 가져온 스레드(풀 스레드)를 사용 할 때의 주의점
 * 풀 스레드의 Name을 설정 할 수 없음 (VS에서 디버깅 진행시에 스레드에 설명 붙히는 건 가능)
 * 풀 스레드는 항상 배경 스레드임
 * 풀 스레드를 차단하면 성능이 떨어질 수 있음
- 우선순위를 변경하는데는 제약이 없음. 풀에 되돌아가면 우선 순위가 보통으로 복구 됨
- IsThreadPoolThread 속성으로 풀 스레드인지 확인 가능
- 내부적으로 스레드 풀을 사용하는 구성요소
 * WCF, Remoting, ASP.NET, ASMX Web Services 응용 프로그램 서버
 * System.Timers.Timer, System.Threading.Timer
 * System.Timers.Timer, System.Threading.Timer
 * 병렬 프로그래밍 수단들
 * BackgroundWorker (쓸모 없어짐)
 * 비동기 대리자 (쓸모 없어짐)

 

풀 스레드 실행

1. Task.Run 메서드를 이용

Task a = Task.Run(() => Console.WriteLine("스레드 풀 안에서 실행 중..."));
a.Wait();

2. .NET 4.0 이전(Task가 없을 때)

// Task와 달리 Wait 기능이 없어서
// ManualResetEvent를 이용하여 Wait을 구현한다
var resetEvent = new ManualResetEvent(false);
 
ThreadPool.QueueUserWorkItem(notUsed =>
{
    Console.WriteLine("스레드 풀 안에서 실행 중...");
    resetEvent.Set();
});
resetEvent.WaitOne();

예외 처리- 스레드를 생성, 실행하는 코드가 속한 try/catch/finally 블록은 생성, 실행된 스레드와 무관

반응형

'C#' 카테고리의 다른 글

[C#] MemoryStream  (0) 2023.11.24
[C#] FileStream  (0) 2023.11.20
[C#] Stream  (0) 2023.11.17
[C#] Task  (0) 2023.11.06
[C#] Stopwatch  (0) 2023.10.30
[C#] PerformanceCounter, PerformanceCounterCategory  (0) 2023.10.28
[C#] EventLog (Windows 이벤트 로그)  (0) 2023.10.26
[C#] StackTrace, StackFrame  (0) 2023.10.24