이번에는 스레드(Thread)의 사용에 대해 알아보겠습니다. 스레드를 알아보기전 프로세스가 무엇인지는 기본적으로 알고있어야합니다.
프로세스는 실행파일이 실행되어 메모리에 적재된 인스턴스입니다. Program.exe가 실행파일 일때, 이 실행 파일을 실행한 것이 프로세스입니다.
프로세스는 실행파일이 실행되어 메모리에 적재된 인스턴스입니다. Program.exe가 실행파일 일때, 이 실행 파일을 실행한 것이 프로세스입니다.
또한 프로세스는 반드시 하나 이상의 스레드로 구성되며, 스레드는 운영체제가 CPU 시간을 할당하는 기본 단위입니다.
이제 스레드 다루는 방법을 알아보겠습니다. .NET 프레임워크는 스레드를 나타내는 클래스로
System.Threading.Thread를 제공합니다. 이 클래스를 사용하는 방법은 아래 순서와 같습니다.
1.Thread의 인스턴스를 생성합니다. 이 때 생성자의 매개 변수로 스레드가 실행할 메소드를 매개 변수로 넘겨줍니다.
2. Thread.Start() 메소드를 호출하여 스레드를 시작합니다.
3. Thread.Join() 메소드를 호출하여 스레드가 끝날 때까지 기다립니다.
1
|
using System.Threading;
| cs |
스레드를 사용하려면 위 네임스페이스가 필요합니다.
스레드 생성방법에 대해서 코드로 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 스레드가 실행할 메소드
public void ThreadFunction()
{
for(int i = 0; i < 5; ++i)
{
Console.WriteLine("ThreadFunction : {0}", i);
}
}
public void CreateThread()
{
// 스레드 인스턴스 생성
Thread thread = new Thread(new ThreadStart(ThreadFunction));
// 스레드를 시작합니다.
thread.Start();
// 스레드를 종료합니다.
thread.Join();
}
| cs |
위 코드에서 실제 스레드가 메모리에 적재되는 시점은 thread.Start() 메소드를 호출했을 때입니다. Thread클래스의 인스턴스는 준비만 해둘 뿐이지요.
Thread.Start() 메소드가 호출되고 나면, CLR은 스레드를 실제로 생성하여 ThreadFunction() 메소드를 호출합니다.
Thread.Join() 메소드는 블록되어 있다가 ThreadFunction() 메소드의 실행이 끝나면, 즉 스레드의 실행의 끝나면 반환되어 다음 코드를 실행할 수 있게 합니다.
* 스레드가 실행하는 메소드에서 for문에 sleep을 걸어두는 경우가 있습니다. 이 경우는 다른 스래드도 CPU를 사용할 수 있도록 CPU 점유를 내려놓기 위함입니다.
1
2
3
4
5
|
for(int i = 0; i < 5; ++i)
{
Console.WriteLine("ThreadFunction : {0}", i);
thread.Sleep(10);
}
| cs |
프로세스 안에서 실행중인 스레드를 임의로 종료시키는 방법에 대해 알아보겠습니다.
스레드의 종료를 시키는 함수로는 Thread.Abort() 함수입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public void ThreadFunction()
{
try
{
for (int i = 0; i < 5; ++i)
{
Console.WriteLine("ThreadFunction : {0}", i);
}
}
catch( ThreadAbortException )
{
}
finally
{
}
}
public void CreateThread()
{
// 스레드 인스턴스 생성
Thread thread = new Thread(new ThreadStart(ThreadFunction));
// 스레드를 시작합니다.
thread.Start();
thread.Abort();
thread.Join();
}
| cs |
Abort() 메소드를 사용할 때 고려해야할 점은, Abort() 메소드를 호출한다고 해서 동작하던 스레드가 즉시 종료된다는 보장이 없습니다.
Thread 객체의 Abort() 메소드를 호출하면 CLR은 해당 스레드가 실행 중이던 코드에 ThreadAbortException을 호출합니다. 이 때 이 예외를 catch하는 코드가 있으면 이 예외를 처리한 다음, finally블록까지 실행한 후에야 해당 스레드는 완전히 종료가 됩니다.
* 하지만 이러한 Abort() 기능은 사용하지 않도록 권장합니다. 멀티스레드를 사용 중인 프로세스에서 한 스레드가 동기화를 위해 어떤 자원을 독점하고자
잠근 후 그 자원을 해제하지 못한 채 Abort() 메소드를 호출당해 갑자기 죽어버리면, 그 자원에 접근하고자 하는 다른 스레드들은 그대로 대기 상태에 빠지게 됩니다.
그래서 쓰레드 실행함수 내에 반복문을 포함하는 코드가 있을 때, Abort() 메소드를 사용하는 대신 그 반복문을 매회 반복할 때마다 계속 수행할 것인지를 확인하는 변수를 다른 스레드들과 공유하고, 그 변수의 값을 외부 스레드에서 변경하도록 하는 것이 나은 방법이라고 합니다.
스레드를 임의로 멈추는 방법에는 인터럽트라는 방법이 또 있습니다.
스레드는 수명이 다해 스스로 종료하는 것이 가장 좋지만, 불가피하게 스레드를 강제 종료시켜야 하는 경우가 있지요, 하지만 그래도 Thread.Abort()메소드는 무자비한 종료 방법입니다. Abort() 메소드를 사용할 때는 도중에 강제로 중단된다 해도 프로세스 자신이나 시스템에 영향이 없는 작업에 한해 사용하는 것이 좋습니다.
스레드의 강제 종료의 경우 시스템에 악영향을 미칠 수 있다면 조금더 부드러운 방법인 Thread.Interrupt()
메소드를 사용합니다.
인터럽트는 스레드가 한참 동작(Running) 중인 상태를 피해서 WaitJoinSleep 상태에 들어갔을 때 ThreadInterruptedException 예외를 던져 스레드를 중지시킵니다.
인터럽트는 스레드가 이미 WaitSleepJoin상태이면 즉시 중단시키지만, 이 상태가 아닌 경우 지켜보다가 WaitSleepJoin상태가 되면 그제서야 스레드를
중단시킵니다. 이러한 특징 때문에 절대로 중단되면 안되는 작업을 하고 있을 때는 중단되지 않는다는 보장을 받을 수 있습니다.
사용방법은 Abort()와 거의 비슷합니다. 아까 Abort()예제코드에서 Abort()함수를 Interrupt()로 변경하여 호출해주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public void ThreadFunction()
{
try
{
for (int i = 0; i < 5; ++i)
{
Console.WriteLine("ThreadFunction : {0}", i);
}
}
catch( ThreadAbortException )
{
}
finally
{
}
}
public void CreateThread()
{
// 스레드 인스턴스 생성
Thread thread = new Thread(new ThreadStart(ThreadFunction));
// 스레드를 시작합니다.
thread.Start();
thread.Interrupt();
thread.Join();
}
| cs |
스레드의 상태 변화
스레드는 여러 가지 상태 변화를 겪습니다. .NET 프레임워크는 스레드의 상태를 ThreadState 열거형에 정의해두었는데, 한번 보겠습니다.
Unstarted
|
스레드 객체를 생성한 후 Thread.Start() 메소드가 호출되기 전의 상태입니다.
|
Running
|
스레드가 시작하여 동작 중인 상태는 나타냅니다. Unstarted상태의 스레드를 Thread.Start()메소드를 통해 이 상태로 만들 수 있습니다.
|
Suspended
|
스레드의 일시 중단 상태를 나타냅니다. 스레드를
Thread.Suspend()메소드를 통해 이 상태로 만들 수 있으며, Suspended 상태인 스레드는 Thread.Resume()메소드를 통해 다시 Running상태로 만들 수 있습니다.
|
WaitSleepJoin
|
스레드가 블록(Block)된 상태를 나타냅니다. Monitor.Enter(), Thread.Sleep(), Thread.Join() 메소드를 호출하면 이상태가 됩니다.
|
Aborted
|
스레드가 취소된 상태를 나타냅니다. Thread.Abort()메소드를 호출하면 이 상태가 됩니다. 이 상태가 된 스레드는 다시 Stopped상태로 전환되어 완전히 중지됩니다.
|
Stopped
|
중지된 스레드의 상태를 나타냅니다. Abort()메소드를 호출하거나 스레드가 실행 중인 메소드가 종료되면 이 상태가 됩니다.
|
Background
|
스레드가 백그라운드로 동작하고 있음을 나타냅니다. 포어그라운드스레드는 하나라도 살아 있는 한 프로세스가 죽지 않습니다만, 백그라운드는 하나가 아니라 열 개가 살아 있어도 프로세스가 죽고 사는 것에는 영향을 미치지 않습니다. 하지만 프로세스가 죽으면 백그라운드 스레드들도 모두 죽습니다. Thread.Background속성에 true값을 입력함으로써 스레드를 이 상태로 바꿀 수 있습니다.
|
그리고 스레드는 상태변화에 있어서 규칙이 있습니다. 예를 들어 Aborted상태에서 절대 Running 상태로 전이될 수 없고, Running상태에서 UnStarted상태로 바뀔수 없습니다. 아래 그림은 스레드 상태 간의 관계를 보여줍니다.
단 Background는 그저 스레드가 어떻게 동작하고 있는지에 관한 정보를 나타낼 뿐이기 때문에 위 관계에서는 표시하지 않았습니다.
스레드의 상태를 나타내는 ThreadState는 Flags 애트리뷰트를 가지고 있으며, Flags는 자신이 수식하는 열거형을 비트 필드, 즉 플래그 집합으로 처리하고 있습니다. 그래서 동시에 한 개 이상의 값을 가질 수 있죠,
* 비트필드란?
바이트 단위가 아닌 비트 단위로 선언한 필드를 말하는데, 주로 비트 단위의 플래그를 표현하기 위해 사용합니다. 0,1,2,3, …,7 정도의 값을 갖는 플래그를 표현하기에 한 바이트 (0~255)를 사용하기에는 메모리 낭비가 되겠지요. 하지만, 현재는 메모리나 프로세서의 용량이 매우 풍부해진 시대이기 때문에 반드시 사용해야할 문법은 아닙니다.
가령 Suspended상태이면서도 WaitSleepJoin 상태를 가질 수도 있고, Background 상태이면서도 Stopped 상태일 수도 있습니다.