1. "시작하세요. C# 프로그래밍" 책에서 P416 ~ P449 스터디하시고 정리해 주세요.
스레딩
***스레드(thread)***는 명령어를 실행하기 위한 스케줄링 단위이며, 프로세스 내부에서 생성할 수 있다.
윈도우 운영체제는 프로세스를 생성할 때 기본적으로 한 개의 스레드를 함께 생성하며, 이를 주 스레드(main thread, primary thread)라고 한다.
스레드는 CPU의 명령어 실행과 관련된 정보를 보관하고 있는데, 이를 스레드 문맥(thread context)이라 한다.
운영체제 스케줄러의 스케줄링 알고리즘에 따라 스레드의 문맥 교환(context switching)이 발생한다.
단일 스레딩의 경우 스레드가 작업 중에는 다른 작업을 할 수 없다. 멀티 스레딩은 스레드가 동시에 동작하기 때문에 다중 작업이 가능하다.
System.Threading.Thread
프로그램이 실행되면 주 스레드가 하나 실행되고 새로 생성된 스레드를 포함한 모든 스레드가 종료되어야 프로그램이 종료된다.
- 전경 스레드(foreground thread) : 프로그램 실행 종료에 영향을 미치는 스레드
- 배경 스레드(background thread) : 프로그램 실행 종료에 영향을 미치지 않는 스레드
// 스레드 생성, 시작, 대기
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(threadFunc); // 스레드 생성
t.IsBackground = true; // 배경 스레드로 변경
t.Start(); // t 스레드 시작
t.Join(); // t 스레드가 종료할 때 까지 현재 스레드를 무한 대기
Console.WriteLine("주 스레드 종료");
}
static void threadFunc()
{
Console.WriteLine("60초 후에 프로그램 종료");
Thread.Sleep(1000 * 60); // 60초 동안 실행 중지
Console.WriteLine("스레드 종료!");
}
}
// 60초 후에 프로그램 종료
// 스레드 종료!
// 주 스레드 종료!
System.Threading.Monitor
멀티 스레딩 환경에서 여러 스레드가 동시에 실행되는 경우 여러 스레드에서 공유 리소스(shared resource)에 접근이 자유로우므로 예측할 수 없는 동작이 발생할 수 있다. 따라서 공유 리소스에 대한 스레드 동기화(synchronization) 처리가 필요하다. BCL에서 스레드 동기화를 위한 Monitor 클래스를 제공한다.
메서드도 여러 스레드에서 동시 접근이 가능하므로 동기화가 필요하다. BCL의 모든 정적 멤버는 다중 스레드 접근에 안전하지만, 인스턴스 멤버는 안전하지 않기 때문에, 동기화가 필요할 때는 직접 동기화 처리를 해야 한다.
class Program
{
int number = 0;
static void Main(string[] args)
{
Thread t = new Thread(threadFunc);
t.Start();
}
static void threadFunc(object inst)
{
Program pg = inst as Program;
for (int i = 0; i < 100000; i++)
{
// Monitor 클래스를 이용한 동기화
Monitor.Enter(pg);
try
{
pg.number = pg.number + 1;
}
finally
{
Monitor.Exit(pg);
}
// lock 예약어를 이용한 동기화 간편 표기
lock (pg)
{
pg.number = pg.number + 1;
}
}
}
}
System.Threading.Interlocked
다중 스레드의 공유자원을 사용하는 몇몇 패턴에 대해서 명시적인 동기화 작업을 단순하게 만드는 정적 메서드를 제공한다.
Interlocked
타입의 정적 메서드로 제공되는 연산의 단위를 원자적 연산(atomic operation) 이라고 하는데, 더 이상 쪼갤 수 없는 연산 단위로 이해할 수 있다.
Interlocked 정적 메서드
-
CompareExchange : 두 대상을 비교하여 값이 같으면 지정된 값을 설정하고, 그렇지 않으면 연산을 수행하지 않는다.
-
Decrement : 지정된 변수의 값을 감소시키고 저장한다.
-
Exchange : 변수를 지정된 값으로 설정한다.
-
Increment : 지정된 변수의 값을 증가시키고 저장한다.
System.Threading.ThreadPool
임시적인 목적으로 스레드를 사용할 수 있는 스레드 풀. 스레드를 자주 생성해서 사용하는 경우 Thread 객체를 생성하기보다는 TheadPool로 부터 재사용했을 때 더 나은 성능을 보인다.
TheadPool.QueueUserWorkItem(threadFunc, data);
System.Threading.EventWaitHandle
Monitor 타입처럼 스레드 동기화 수단의 하나다.
이벤트 객체는 Signal과 Non-Signal 상태를 갖는다.
이벤트 객체의 상태는 Set
, Reset
메서드로 전환한다.
WaitOne을 통해 이벤트 객체의 상태를 판단하여 제어를 반환하거나 대기한다.
이벤트는 수동 리셋(manual reset)과 자동 리셋(auto reset)으로 나뉜다.
- 수동 리셋(manual reset) :
Set
메서드를 호출하여 Signal 상태가 된 후에 계속 Signal 상태로 머무는 이벤트.Reset
메서드를 호출하여 Non-Signal 상태로 돌릴 수 있다. - 자동 리셋(auto reset) :
Set
메서드를 호출하여 Signal 상태가 된 후에 바로 Non-Signal 상태로 자동으로 변경되는 이벤트.
비동기 호출(Asynchronous Call)
비동기 호출은 동기 호출(Synchronous Call)에서 발생하는 스레드 점유 문제를 해결하기 위해 제공된다.
C#에서는 스트림 객체의 비동기 호출을 위해 Read/Write 메서드에 대해 각각 BeginRead
/EndRead
, BeginWrite
/EndWrite
메서드를 쌍으로 제공한다.
System.Delegate의 비동기 호출
C#에서는 입출력 장치뿐만 아니라 일반 메서드에 대해서도 델리게이트를 통한 비동기 호출을 제공한다.
델리게이트의 비동기 호출을 위한 메서드 BeginInvoke
/EndInvoke
를 사용하면 ThreadPool을 이용한 비동기 호출을 사용할 수 있다.
public class Calc
{
public static long Cumsum(int start, int end)
{
long sum = 0;
for (int i = start; i <= end; i++)
{
sum += i;
}
return sum;
}
}
class Program
{
public delegate long CalcMethod(int start, int end);
static void Main(string[] args)
{
CalcMethod calc = new CalcMethod(Calc.Cumsum);
IAsyncResult ar = calc.BeginInvoke(1, 100, null, null);
ar.AsyncWaitHandle.WaitOne();
long result = calc.EndInvoke(ar);
Console.WriteLine(result);
}
}