2013년 12월 29일 일요일

닷넷,C#에서의 쓰레드(.NET C# Thread), 멀티쓰레드[재직자무료교육/프로그래머교육/구로디지털IT교육,오라클/자바/닷넷/C#/iOS/안드로이드/아이폰교육]

닷넷,C#에서의 쓰레드(.NET C# Thread), 멀티쓰레드[재직자무료교육/프로그래머교육/구로디지털IT교육,오라클/자바/닷넷/C#/iOS/안드로이드/아이폰교육] 


닷넷,C#에서의 쓰레드(.NET C# Thread), 멀티쓰레드
 
쓰레드란 CPU를 이용하는 가장 작은 단위인데 싱글 쓰레드를 사용하면 CPU의 사용 권한을 하나의 쓰레드가 독차지 하게 된다. 지하철 개찰구의 예를들면 개찰구가 하나라면 평상시에는 큰 무리가 아니지만 출퇴근 시간에는 무리가 따를 수 있다. 결국 개찰구를 늘여야 한다는 것이다. 프로그래밍에서 볼 때 개찰구를 늘이는 것이 멀티 쓰레드를 이용하는 것이다. 멀티 쓰레드가 제대로 동작하기 위해서는 CPU가 여러 개 있어야 한다. 보통은 단일 CPU를 사용 할 것이다. CPU는 한번에 하나의 쓰레드를 사용하므로 멀티 쓰레드 프로그램이 실행되는 경우에는 CPU의 사용 시간을 나누어서 각각의 쓰레드에게 나누어 주는 것이다. 결국 개찰구는 늘였지만 표를 파는 곳은 한 군데 인것이다.
 
C#에서의 멀티 쓰레드
 
C#에서 멀티 쓰레드를 사용하는 방법은 기 정의된 쓰레드 클래스를 사용하면 된다. 결국 배워야 하는 것은 C#에서 멀티 쓰레드를 위한 클래스가 어떤 것이 있고 그 사용법은 어떻게 되는가 이다. 쓰레드를 위한 개체들은 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 SecondWork()
        {
               for(int i=0; i < 100; i++)
               {
                       Thread.Sleep(1000);
                       Console.Write("S{0} " ,i);
               }
        }
 
        [MTAThread]
        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));
 
               first.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();
               }
        }
}
 
 
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();
                       }
               }
        }
}
 
class TestMain
{
        [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();
        }
}
Suspend 메소드로 쓰레드를 잠시 중지하면 Resume 메소드로 다시 동작하게 할 수 있지만 Thread.Abort로 일단 쓰레드를 종료 시키면 다시 되살릴 수 없다.쓰레드가 확실히 종료 되었는지를 살펴보기 위해서는 Thead.Join 메소드를 사용 할 수 있는데 Thread.Join 메소드는 동기적으로 동작하므로 쓰레드가 종료 할 때까지 기다리게 된다. 만일 어떤 이유에서든지 쓰레드가 종료 되지 않는 다면 무한정 기다릴 수 밖에  없다. Thread.Join 메소드 안에 1/1000초 단위로 파라미터를 전달 할 수 있는데이것은 얼마 만큼 Join으로 붙은 쓰레드가 종료 될 때까지 기다릴 수 있는 가에 대한 값이다. 만일 주어진 시간안에 종료되면 true를 반환하고 기다리기를 중지 한다면 false 값을 반환 한다.
 
쓰레드가 여러 개 있다면 한 쓰레드가 동작하고 나서 어떤 쓰레드가 동작 할까? 아마도 높은 우선 순위를 가지고 있는 쓰레드가 수행 될 것이다. 각각의 쓰레드는 CPU 사용 권한에 대한 우선 순위가 있는데 우선 순위가 높을수록 CPU사용 권한을 먼저 할당 받는다.
 
아래의 예제를 보도록 하자.
 
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(",");
                       }
 
                       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(",");
                       }
 
                       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));
 
               first.Start();
               second.Start();
        }
}
 
위의 예제를 실행해 보면 각각의 쓰레드가 할당 받은 시간이 비슷함을 알 수 있다. 두개의 쓰레드가 우선 순위가 비슷 하니까 아래의 그림과 같은 결과가 나오는 것이다.

 
아래의 예는 Join을 이용한 예제이다.
 first.Join() 부분을 주석으로 막은 후 다시 실행 해 보라.
 
using System;
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);
               }
        }
}
 
class TestMain
{
        [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);
        }
}
 
 
한 쓰레드에 대해 우선순위를 높여 주면 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(",");
                       }
 
                       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(",");
                       }
 
                       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));
 
               first.Priority = ThreadPriority.Lowest;
 
               first.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(",");
                              }
 
                              Console.WriteLine("{0}{1} ", rank, lockString);
                       }
               }
        }
 
        public void FirstWork()
        {
               Print("F");
        }
 
        public void SecondWork()
        {
               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));
 
               first.Start();
               second.Start();
        }
}
 
 
lock 문 이외에 System.Monitor라는 클래스가 있는데 이 Monitor 클래스에는 Enter,Exit 메소드가 있는데 Enter 메소드는 잠금 상황으로, Exit 메소드는 잠금을 해제하는 역할을 한다. 앞에서 작성한 예제를 System.Monitor를 이용하는 예문으로 바꿔 보자…
 
using System;
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(",");
                       }
 
                       Console.WriteLine("{0}{1} ", rank, lockString);
               }
               Monitor.Exit(this);
        }
 
        public void FirstWork()
        {
               Print("F");
        }
 
        public void SecondWork()
        {
               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));
 
               first.Start();
               second.Start();
        }
}
 
 
 
 
 

댓글 없음:

댓글 쓰기