IT_Programming/C#

스레드(thread) 예제

JJun ™ 2007. 12. 23. 10:21

쓰레드(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();
        }
}