Thread 클래스
스래드 생성
- 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 |