C#, 닷넷, 쓰레드(Thread)
쓰레드란 CPU를 이용하는 가장 작은 단위인데 싱글 쓰레드를 사용하면 CPU의 사용 권한을 하나의 쓰레드가 독차지 하게 된다. 지하철 개찰구의 예를들면 개찰구가 하나라면 평상시에는 큰 무리가 아니지만 출퇴근 시간에는 무리가 따를 수 있다. 결국 개찰구를 늘여야 한다는 것이다. 프로그래밍에서 볼 때 개찰구를 늘이는 것이 멀티 쓰레드를 이용하는 것이다. 멀티 쓰레드가 제대로 동작하기 위해서는 CPU가 여러 개 있어야 한다. 보통은 단일 CPU를 사용 할 것이다. CPU는 한번에 하나의 쓰레드를 사용하므로 멀티 쓰레드 프로그램이 실행되는 경우에는 CPU의 사용 시간을 나누어서 각각의 쓰레드에게 나누어 주는 것이다. 결국 개찰구는 늘였지만 표를 파는 곳은 한 군데 인것이다.
C#에서의 멀티 쓰레드
C#에서 멀티 쓰레드를 사용하는 방법은 기 정의된 쓰레드 클래스를 사용하면 된다. 결국 배워야 하는 것은 C#에서 멀티 쓰레드를 위한 클래스가 어떤 것이 있고 그 사용법은 어떻게 되는가 이다. 쓰레드를 위한 개체들은 System.Threading 네임스페이스 안에 정의 되어 있다.
[첫번째 예제]
using System;
using System.Threading;
using System;
using System.Threading;
public class ThreadTest
{
public void FirstWork()
{
for(int i=0; i < 100; i++)
{
Thread.Sleep(1000); //밀리세컨드 단위
Console.Write("F{0} " ,i);
}
}
{
public void FirstWork()
{
for(int i=0; i < 100; i++)
{
Thread.Sleep(1000); //밀리세컨드 단위
Console.Write("F{0} " ,i);
}
}
public void SecondWork()
{
for(int i=0; i < 100; i++)
{
Thread.Sleep(1000);
Console.Write("S{0} " ,i);
}
}
{
for(int i=0; i < 100; i++)
{
Thread.Sleep(1000);
Console.Write("S{0} " ,i);
}
}
[MTAThread]
public static void Main()
{
ThreadTest t = new ThreadTest();
public static void Main()
{
ThreadTest t = new ThreadTest();
//Thread는 생성자에 ThreadStart형 Delegate를 인자로 받는다.
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
first.Start();
second.Start();
}
}
second.Start();
}
}
[예제1-1]
using System;
using System.Threading;
class ThreadTest
{
static void Thmethod()
{
int id = AppDomain.GetCurrentThreadId();
Console.WriteLine("Thread[{0}] Thmethod Method Running",id);
}
static void Main()
{
int id = AppDomain.GetCurrentThreadId();
Console.WriteLine("Main Thread[{0}]",id);
for(int i=0;i<10;i++)
{
Thread th = new Thread(new ThreadStart(Thmethod));
th.Start();
}
}
}
using System;
using System.Threading;
class ThreadTest
{
static void Thmethod()
{
int id = AppDomain.GetCurrentThreadId();
Console.WriteLine("Thread[{0}] Thmethod Method Running",id);
}
static void Main()
{
int id = AppDomain.GetCurrentThreadId();
Console.WriteLine("Main Thread[{0}]",id);
for(int i=0;i<10;i++)
{
Thread th = new Thread(new ThreadStart(Thmethod));
th.Start();
}
}
}
Thread.Sleep 안에는 해당 Thread가 얼마만큼 쉴 건지에 대한 시간을 밀리 세컨드단위로 지정 한다. 만약 시간을 0으로 설정하면 현재 자신에게 주어진 시간을 다른 쓰레드에게 쓰게 하겠다는 의미이다. 지금 당장은 CPU를 사용 하지 않아도 될 때 다른 쓰레드에게 CPU를 사용 할 기회를 줌으로써 CPU를 효율적으로 이용 할 수 있다. 쓰레드를 쉐게 하는 방법은 Thread.Suspend 를 이용 할 수도 있다. Sleep과의 차이는 Sleep인 경우엔 지정한 시간 만큼 쉰다는 의미지만 Suspend인 경우엔 Resume 메소드를 호출 할 때 까지 쉬게 된다는 것이다.또 다른 차이점은 Sleep 메소드는 자기 자신의 쓰레드만 쉬게 할 수 있다. 반면에 Suspend 는 자기 뿐 아니라 다른 쓰레드도 쉬게 할 수 있다.잠을 자고 있는 Thread가 스스로 깰 수는 없다. 그래서 Suspend로 쉬고 있는 쓰레드는 다른 쓰레드가 Resume을 이용하여 깨울 때 까지 쉬고 있는 것이다. 주의 할 점은 Sleep인 경우는 쓰레드가 즉시 중단 되지만 Suspend로 쉬게 할려면 즉시 중단 되지 않는다.
[예제2]
using System;
using System.Threading;
public class ThreadTest2
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
Console.WriteLine("F{0}", i);
if (i == 5)
{
sleep = true;
Console.WriteLine("");
Console.WriteLine("first 쉼...");
Thread.CurrentThread.Suspend();
}
}
}
}
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
Console.WriteLine("F{0}", i);
if (i == 5)
{
sleep = true;
Console.WriteLine("");
Console.WriteLine("first 쉼...");
Thread.CurrentThread.Suspend();
}
}
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest2 t = new ThreadTest2();
Thread first = new Thread(new ThreadStart(t.FirstWork));
{
[MTAThread]
public static void Main()
{
ThreadTest2 t = new ThreadTest2();
Thread first = new Thread(new ThreadStart(t.FirstWork));
first.Start();
while(t.sleep == false) {}
Console.WriteLine("");
Console.WriteLine("first를 깨웁니다...");
first.Resume();
}
}
Console.WriteLine("first를 깨웁니다...");
first.Resume();
}
}
Suspend 메소드로 쓰레드를 잠시 중지하면 Resume 메소드로 다시 동작하게 할 수 있지만 Thread.Abort로 일단 쓰레드를 종료 시키면 다시 되살릴 수 없다.쓰레드가 확실히 종료 되었는지를 살펴보기 위해서는 Thead.Join 메소드를 사용 할 수 있는데 Thread.Join 메소드는 동기적으로 동작하므로 쓰레드가 종료 할 때까지 기다리게 된다. 만일 어떤 이유에서든지 쓰레드가 종료 되지 않는 다면 무한정 기다릴 수 밖에 없다. Thread.Join 메소드 안에 1/1000초 단위로 파라미터를 전달 할 수 있는데이것은 얼마 만큼 Join으로 붙은 쓰레드가 종료 될 때까지 기다릴 수 있는 가에 대한 값이다. 만일 주어진 시간안에 종료되면 true를 반환하고 기다리기를 중지 한다면 false 값을 반환 한다.
쓰레드가 여러 개 있다면 한 쓰레드가 동작하고 나서 어떤 쓰레드가 동작 할까? 아마도 높은 우선 순위를 가지고 있는 쓰레드가 수행 될 것이다. 각각의 쓰레드는 CPU 사용 권한에 대한 우선 순위가 있는데 우선 순위가 높을수록 CPU사용 권한을 먼저 할당 받는다.
아래의 예제를 보도록 하자.
using System;
using System.Threading;
using System.Threading;
public class ThreadTest3
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("F{0}", i);
}
}
}
}
public void SecondWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("S{0}", i);
}
}
}
}
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
first.Start();
second.Start();
}
}
second.Start();
}
}
위의 예제를 실행해 보면 각각의 쓰레드가 할당 받은 시간이 비슷함을 알 수 있다. 두개의 쓰레드가 우선 순위가 비슷 하니까 아래의 그림과 같은 결과가 나오는 것이다.
아래의 예는 Join을 이용한 예제이다.
first.Join() 부분을 주석으로 막은 후 다시 실행 해 보라.
using System;
using System.Threading;
using System.Threading;
public class ThreadTest2
{
public int[] iArray = new int[100];
public void CollectData()
{
for(int i=0; i <= 20; i++)
{
iArray[i] = i+1;
Console.Write(",");
Thread.Sleep(500);
}
}
}
{
public int[] iArray = new int[100];
public void CollectData()
{
for(int i=0; i <= 20; i++)
{
iArray[i] = i+1;
Console.Write(",");
Thread.Sleep(500);
}
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest2 t = new ThreadTest2();
Thread first = new Thread(new ThreadStart(t.CollectData));
first.Start();
first.Join();
{
[MTAThread]
public static void Main()
{
ThreadTest2 t = new ThreadTest2();
Thread first = new Thread(new ThreadStart(t.CollectData));
first.Start();
first.Join();
int sum=0;
for(int i=0; i<t.iArray.Length; i++)
{
sum += t.iArray[i];
}
Console.WriteLine();
Console.WriteLine("sum = {0}", sum);
}
}
for(int i=0; i<t.iArray.Length; i++)
{
sum += t.iArray[i];
}
Console.WriteLine();
Console.WriteLine("sum = {0}", sum);
}
}
한 쓰레드에 대해 우선순위를 높여 주면 CPU 사용 권한을 우선적으로 갖는 것이다. Thread의 우선 순위와 관련된 프로퍼티가 있는데 열거형 값인 Highest, AboveNormal, Normal, BelowNormal, Lowest중 한 값이다. 아래의 예제를 살펴 보자.
using System;
using System.Threading;
public class ThreadTest3
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
public bool sleep = false;
public void FirstWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("F{0}", i);
}
}
}
}
public void SecondWork()
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("S{0}", i);
}
}
}
}
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
first.Priority = ThreadPriority.Lowest;
first.Start();
second.Start();
}
}
second.Start();
}
}
멀티쓰레드 환경인 경우 여러 곳에서 같은 객체의 메소드를 호출 하는 경우에 예기치 않은 결과가 나타날 수 있다. 어떤 메소드의 사용을 한 쓰레드가 끝난 후 다른 쓰레드가 접근하게 하려면 lock 문을 사용 한다. 다음의 예제를 보도록 하자.
using System;
using System.Threading;
public class ThreadTest3
{
public string lockString = "Hello";
public void Print(string rank)
{
//lock을 걸어준 구문은 처음 쓰레드가 끝날때 까지 다른 쓰레드가 접근 금지
lock (this)
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
public string lockString = "Hello";
public void Print(string rank)
{
//lock을 걸어준 구문은 처음 쓰레드가 끝날때 까지 다른 쓰레드가 접근 금지
lock (this)
{
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("{0}{1} ", rank, lockString);
}
}
}
}
}
}
public void FirstWork()
{
Print("F");
}
{
Print("F");
}
public void SecondWork()
{
Print("S");
}
}
{
Print("S");
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
first.Start();
second.Start();
}
}
second.Start();
}
}
lock 문 이외에 System.Monitor라는 클래스가 있는데 이 Monitor 클래스에는 Enter,Exit 메소드가 있는데 Enter 메소드는 잠금 상황으로, Exit 메소드는 잠금을 해제하는 역할을 한다. 앞에서 작성한 예제를 System.Monitor를 이용하는 예문으로 바꿔 보자…
using System;
using System.Threading;
using System.Threading;
public class ThreadTest3
{
public string lockString = "Hello";
public void Print(string rank)
{
//lock을 걸어준 구문은 처음 쓰레드가 끝날때 까지 다른 쓰레드가 접근 금지
Monitor.Enter(this);
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
{
public string lockString = "Hello";
public void Print(string rank)
{
//lock을 걸어준 구문은 처음 쓰레드가 끝날때 까지 다른 쓰레드가 접근 금지
Monitor.Enter(this);
for(int i=0; i < 10; i++)
{
for(int j=0; j < 10; j++)
{
Thread.Sleep(100);
Console.Write(",");
}
Console.WriteLine("{0}{1} ", rank, lockString);
}
Monitor.Exit(this);
}
}
Monitor.Exit(this);
}
public void FirstWork()
{
Print("F");
}
{
Print("F");
}
public void SecondWork()
{
Print("S");
}
}
{
Print("S");
}
}
class TestMain
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
{
[MTAThread]
public static void Main()
{
ThreadTest3 t = new ThreadTest3();
Thread first = new Thread(new ThreadStart(t.FirstWork));
Thread second = new Thread(new ThreadStart(t.SecondWork));
first.Start();
second.Start();
}
}
second.Start();
}
}
댓글 없음:
댓글 쓰기