메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

C# 쓰레드 이야기: 4. 쓰레드 기본 개념

한빛미디어

|

2001-11-21

|

by HANBIT

36,474

By 한동훈 필자는 쓰레드에 대해서 글을 쓰지만, 전혀 쓰레드에 대한 전문가가 아니라는 사실을 독자들이 알았으면 싶다. 사람들은 종종
"쓰레드? 그건 왜 쓰는데? 백그라운드 프로세스로 여러 개를 동시에 처리하면 되잖아.."
라고 얘기하곤 한다. 그래서 이번에는 실제 코드와는 관계없이 쓰레드의 기본 개념에 대해서 설명하고자 한다. 물론 여기서 소개하는 기본 개념은 공통 언어 런타임(이하 CLR, Common Language Runtime), 윈도우, 유닉스와는 전혀 관계가 없다. 이들 운영체제에서 쓰레드를 관리하는 것에 대한 기본을 설명하고자 하는 것이다. 그러니 윈도우에서 이렇게 쓰레드를 처리한다라고 생각하지 말 것을 당부한다. 쓰레드 스케줄링 윈도우 2000의 작업 관리자에서 현재 수행중인 프로세스와 쓰레드의 숫자를 볼 수 있다. 동시에 "저만큼이 과연 실행될 수 있을까? 지금 내 PC에는 CPU가 한 개 뿐인데?" 라는 의문을 가질 것이다. 그리고 가장 많은 설명을 들었던 것이 시분할(Time Sharing)에 의해서 가능하다고 한다. 참고: 정확히 말하면 NT 운영체제는 시분할이 아닌 타임 슬라이싱(Time Slicing)을 사용한다. 이에 대해서는 나중에 논의하겠다. 반면에 Solaris와 같은 운영체제는 시분할을 사용한다. 시분할이라는 것은 CPU의 실행시간을 100이라 보았을 때 10으로 10개를 나누어서 10개의 쓰레드를 차례대로 교환하면서 실행하는 것을 말한다. 현재 사용하는 대부분의 운영체제는 32bit 운영체제이고 따라서 4GB의 어드레싱 영역을 얻을 수가 있다. 각각의 4G 영역을 2G씩 나누어서 상위 2G에는 시스템 코드, DLL, 프로세스 간 공유되는 데이터와 코드가 위치하고, 하위 2G에는 프로그램을 실행하는 프로세스, 응용 프로그램의 바이너리 이미지(실행파일), 응용 프로그램 공유 라이브러리(DLL)의 코드와 데이터가 위치한다. A 프로세스를 잠시 중지하고 B 쓰레드를 실행하는 것과 같이 프로세스와 프로세스 사이에 변환하는 것은 프로세스 문맥 교환(Process Context Exchange)이라 한다. 마찬가지로 하나의 프로세스안에 있는 여러 개의 쓰레드가 서로 실행되는 것을 쓰레드 문맥 교환(Thread Context Switching)이라고 한다. 이와 같이 프로세스와 쓰레드간에 문맥 교환을 어떻게 처리하는 가를 담당하는 것이 멀티태스킹 운영체제의 스케줄러다. 스케줄러는 어떤 쓰레드를 다음에 동작시킬 것인지를 결정한 뒤에 선택한 쓰레드를 동작시켜서 동시에 여러 가지 일을 처리하도록 한다. 즉, 운영체제 스케줄러의 최대 목표는 CPU를 최대한 활용하여 PC의 성능을 최대한 활용하는 것이다. 우선 순위 우선 순위에는 두 가지가 있다. 하나는 프로세스간에 우선 순위를 결정하는 프로세스 우선 순위가 있고, 하나의 프로세스내에 있는 쓰레드간의 우선 순위를 결정하는 쓰레드 우선 순위가 있다. 대부분의 운영체제에서는 우선 순위를 결정하는데 라운드 로빈(round-robin) 스케줄링 알고리즘을 포함하고 있다. 쓰레드는 FIFO(First-In First-Out) 구조의 큐에 들어가며, 스케줄러는 가장 우선 순위가 높은 쓰레드를 차례대로 수행한다. 스케줄러는 이 큐의 쓰레드를 차례대로 수행하면서 점점 낮은 순위의 큐로 이동한다. 만약, 새로 생성된 쓰레드가 현재 스케줄러가 있는 큐보다 우선 순위가 높은 큐에 들어가게 되면, 스케줄러는 우선 순위가 높은 큐로 이동하여 쓰레드를 수행하게 된다. 쓰레드의 우선 순위는 숫자로 표시하며, 이 숫자값은 운영체제마다 다르다. 윈도우 2000의 작업 관리자에서 해당 프로세스에서 마우스 오른쪽 버튼을 클릭하면 다음과 같이 우선 순위를 설정하는 것을 볼 수 있다.

그림에서 볼 수 있는 것처럼 윈도우 2000 서버는 실시간, 높음, 보통 초과, 보통, 보통 미만, 낮음과 같은 프로세스 우선 순위를 설정할 수 있다. 윈도우의 우선 순위는 32를 기준으로 설정한다. 이 숫자값이 높으면 높을수록 우선 순위가 높다. 다음 표를 참고하자.

우선 순위숫자값플래그C# 플래그
실시간(Time Critical)32THREAD_PRIORITY_TIME_CRITICAL 
실시간(Real Time)24REALTIME_PRIORITY_CLASS 
최상(Highest) THREAD_PRIORITY_HIGHESTThreadPriority.Highest
보통 초과(AboveNormal) THREAD_PRIORITY_ABOVE_NORMALThreadPriority.AboveNormal
보통(Normal)7-9THREAD_PRIORITY_NORMALThreadPriority.Normal
보통 미만(BelowNormal) THREAD_PRIORITY_BELOW_NORMALThreadPriority.BelowNormal
낮음(Lowest) THREAD_PRIORITY_LOWESTThreadPriority.Lowest
휴지(Idle)1THREAD_PRIORITY_IDLE 
[표1] Win32와 CLR 우선순위 표에는 Win32와 CLR의 우선 순위를 각각 정리해 두었다. 표에서 알 수 있는 것처럼 닷넷 환경에서는 다섯 가지의 우선 순위만을 지정할 수 있으며, 시스템의 수행에 결정적인 영향을 줄 수 있는 실시간 우선 순위 등은 지원하지 않는 다는 것을 알 수 있다. 참고로 윈도우 9x 계열의 운영체제는 보통 초과(AboveNormal), 보통 미만(BelowNormal) 우선 순위는 지원되지 않는다. 이러한 우선 순위의 종류나 각각의 숫자값은 운영체제마다 다르다. [표1]의 Win32 플래그는 winbase.h 헤더파일에 정의되어 있다. 일반적으로 독자들이 쓰고 있는 NT 운영체제는 7가지 쓰레드 우선 순위를 지원한다. 대부분의 멀티 쓰레드 프로그래밍을 하는 경우에는 프로세스의 우선 순위보다는 쓰레드의 우선 순위에 많은 신경을 쓰게 된다. 프로세스의 우선 순위는 운영 체제에서 알아서 하는 것이 대부분이지만, 응용 프로그램에서의 쓰레드 우선 순위는 프로그래머가 직접 제어한다. 예를 들어서, 시스템에 특정 파일을 검색하는 검색 프로그램을 작성한다고 하자. 이 경우에 검색을 수행하는 쓰레드와 사용자 인터페이스를 담당하는 쓰레드가 있어야 한다. 검색을 하는 도중에 사용자가 검색을 중지하고자 "중지"버튼을 클릭한다면 바로 반응을 보일 수 있어야 한다. 이 경우에는 언제든지 사용자에게 반응하도록 하기 위해 인터페이스 쓰레드의 우선 순위를 검색 쓰레드보다 상위에 두도록 한다. 닷넷에서의 쓰레드 우선 순위 닷넷에서 쓰레드 우선 순위를 변경하여 프로그램이 어떻게 동작하는지 알아보도록 하자. 먼저 지난 시간에 이용한 예제를 다시 한 번 살펴보도록 하자. 여기서는 Join()문을 없앴으며 각각의 Do 함수들도 Thread.Sleep(50)으로 같은 주기를 갖도록 하였다.
이름: MultiThread.cs

namespace csharp
{
  using System;
  using System.Threading;

  class MultiThreadApp
  {
    public static void Main()
    {
      MultiThreadApp app = new MultiThreadApp();

      app.DoTest();
    }   // End of Main()

    private void DoTest()
    {
      Thread[] aThread =
        {
          new Thread( new ThreadStart(DoPrinting) ),
          new Thread( new ThreadStart(DoSpelling) ),
          new Thread( new ThreadStart(DoSaving) )
        };

      foreach( Thread t in aThread)
      {
        t.Start();
      }
    }   // End of DoTest()

    private void DoPrinting()
    {
      Console.WriteLine("인쇄 시작");
      
      for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++)
      {
        Thread.Sleep(50);
        Console.Write("p|");
      }

      Console.WriteLine("인쇄 완료");
    }   // End of DoPrinting()

    private void DoSpelling()
    {
      Console.WriteLine("철자 검사 시작");
      
      for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++)
      {
        Thread.Sleep(50);
        Console.Write("c|");
      }
      
      Console.WriteLine("철자 검사 완료");
     }  // End of DoSpelling()

    private void DoSaving()
    {
      Console.WriteLine("저장 시작");
 
      for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++)
      {
        Thread.Sleep(50);
        Console.Write("s|");
      }
      Console.WriteLine("저장 완료");
    }   // End Of DoSaving()
  } // End of class MultiThreadApp
} // end of namespace csharp
코드를 위와 같이 고쳤으면 다시 컴파일을 하고 실행해 보도록 하자. 실행결과는 다음과 같을 것이다.

결과에서 볼 수 있는 것처럼 저장, 인쇄, 철자 검사를 시작하고 차례대로 저장, 인쇄, 철자 검사 순으로 완료되는 것을 볼 수 있다. 이제 각각의 쓰레드의 우선 순위를 변경해서 철자 검사를 가장 먼저 끝내고, 인쇄를 가장 마지막에 끝나도록 해보자. 쓰레드 우선 순위는 다음과 같이 설정한다.

someThread.Priority = ThreadPriority.Highest;
또는 다음과 같이 사용할 수도 있다([표1. Win32와 CLR 우선순위] 참고).
someThread.Priority = ThreadPriority.Normal + 2; // Highest 사용
ThreadPriority는 열거형(enum)이고, 사용할 수 있는 종류는 [표1]에 있는 것과 같다. DoTest() 함수를 다음과 같이 바꿔보도록 하자.
Thread[] aThread =
  {
    new Thread( new ThreadStart(DoPrinting) ),
    new Thread( new ThreadStart(DoSpelling) ),
    new Thread( new ThreadStart(DoSaving) )
  };

  // 인쇄 쓰레드의 우선 순위를 낮음으로 설정한다.
  aThread[0].Priority = ThreadPriority.Lowest;
  // 철자 검사 쓰레드의 우선 순위를 높음으로 설정한다.
  aThread[1].Priority = ThreadPriority.Highest;
  Console.WriteLine("인쇄 쓰레드 우선 순위 : " + aThread[0].Priority);
  Console.WriteLine("철자 쓰레드 우선 순위 : " + aThread[1].Priority);
  Console.WriteLine("저장 쓰레드 우선 순위 : " + aThread[2].Priority);
코드를 모두 변경했으면 다시 컴파일하고 실행해 보도록 하자. 결과는 다음과 같을 것이다.

위에서 알 수 있는 것처럼 각각의 쓰레드에 대해서 쓰레드 우선 순위를 지정할 수 있다. [표1]에서 알 수 있는 것처럼 CLR에서는 5가지의 우선 순위를 지정할 수 있다. CLR은 현재 MS의 윈도우에서만 실행되지만 다른 운영체제에서도 실행될 수 있다. 다른 운영체제에서는 3가지의 우선 순위를 가진다면 C#에서 지정한 우선 순위중에 몇 가지 같은 우선 순위로 지정할 것이다. 다행히도 NT는 7가지의 우선 순위를 가지지만, 윈도우 95/98과 같은 운영체제는 AboveNormal과 BelowNormal 우선 순위를 운영 체제의 다른 우선 순위로 지정할 것이다. 쓰레드 우선 순위와 스케줄러 멀티 쓰레드 응용 프로그램에서 쓰레드에 대해서 다른 우선 순위를 지정하여 실행 순서를 다르게 할 수 있다. 그러면 같은 우선 순위를 가진 쓰레드가 여러 개 있는 경우에는 어떤 쓰레드가 우선 순위를 가지게 될까? 이 경우에는 어떤 쓰레드가 우선 순위를 갖는 다고 보장할 수 없다. 이것은 전적으로 호스트되는 OS의 쓰레드 스케줄러에 달려있다. CLR은 같은 우선 순위를 갖는 쓰레드가 공평하게 실행된다고 보장하지 않는다. 같은 순위의 쓰레드를 차례대로 실행할 수도 있고, 무작위로 실행할 수도 있다. 심지어는 제어권을 양보한 쓰레드를 다시 실행할 수도 있다. OS의 쓰레드 스케줄러가 동일 우선 순위를 갖는 쓰레드를 어떻게 스케줄링하든지 간에 한 번 실행된 쓰레드가 다시 실행되는 것을 방지하려면 쓰레드의 루프안에 Sleep()을 사용하도록 해야 한다. 탐욕 쓰레드(Selfish Thread) 지금까지 글을 읽은 독자중에 자바와 같은 언어에서 쓰레드 프로그래밍을 한 경험이 있다면 이상하다고 여기는 부분이 있을 것이다. 지금까지 필자가 만든 예제들은 모두 한 가지 작업을 할 때마다 제어권을 양보하고 있다. 마지막에 만든 코드의 실행결과를 살펴보아도 각각의 쓰레드 우선 순위가 Highest, Normal, Lowest인데도 불구하고, 실행 순서와 종료 순서가 바뀐 것 이외에는, "c", "p", "s"가 사이 좋게 번갈아가며 찍히는 것을 보았을 것이다. 각각 다른 우선 순위를 갖는 쓰레드가 사이좋게 제어권을 양보하면서 차례대로 실행될 수 있는 것은 각각의 루프안에 있는 Sleep() 때문이다. Sleep() 함수가 없는 쓰레드의 실행시간이 길다면 홀로 스레드 실행시간을 독차지 하는 것을 볼 수 있다. 이와 같은 쓰레드를 탐욕 쓰레드라고 한다. 탐욕 쓰레드를 알아보기 위해 각각의 Do 함수의 Thread.Sleep()을 지우고, 루프의 횟수를 2000회로 늘렸다(이와 같이 루프를 늘리는 것은 시스템에 따라 정상적으로 쓰레드 교환(Switching)이 일어나더라도 한 번에 200∼270회의 출력을 할 수 있어 교환이 일어나는 것을 관찰할 수 없기 때문이다). 다시 한 번 전체 소스를 살펴보도록 하자.

이름: Selfish.cs

namespace csharp
{
  using System;
  using System.Threading;

  class MultiThreadApp
  {
    public static void Main()
    {
      MultiThreadApp app = new MultiThreadApp();

      app.DoTest();
    }

    private void DoTest()
    {
        Thread[] aThread =
        {
          new Thread( new ThreadStart(DoPrinting) ),
          new Thread( new ThreadStart(DoSpelling) ),
          new Thread( new ThreadStart(DoSaving) )
        };

 
        aThread[1].Priority = ThreadPriority.Normal + 2;
 
        foreach( Thread t in aThread)
        {
          t.Start();
        }

        foreach( Thread t in aThread)
        {
          t.Join();
        }

        Console.WriteLine("모든 쓰레드가 종료되었습니다");
    } 

    private void DoPrinting()
    {
      Console.WriteLine("인쇄 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
        Console.Write("p|");
      }

      Console.WriteLine("인쇄 완료");
    }

    private void DoSpelling()
    {
      Console.WriteLine("철자 검사 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
        Console.Write("c|");
      }
     
      Console.WriteLine("철자 검사 완료");
    }

    private void DoSaving()
    {
      Console.WriteLine("저장 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
Console.Write("s|");
      }
      Console.WriteLine("저장 완료");
    }
  }

}
실행 결과를 살펴보면 다른 쓰레드가 시작하기 전에 철자 검사 쓰레드가 시작하고 종료되는 것을 알 수 있다. Thread.Sleep()이 없기 때문에 그런 것이지 원래 쓰레드는 이렇게 실행되는 것이다. 실행시간이 너무 짧아서 제대로 확인할 수 없다고 의심할 수도 있을 것이다. -_-; 그러면 위 코드에서 다음 부분을 주석 처리하고 다시 컴파일하여 실행해 보기 바란다.
// aThread[1].Priority = ThreadPriority.Normal + 2;
실행 결과는 각각의 쓰레드가 비슷하게 시작하고, 비슷하게 종료된다. 만약 파일을 검색하는 프로그램을 만든다고 가정하자. 이때 파일을 검색하는 쓰레드에 Sleep() 구문이 없다면 화면에 버튼을 클릭하려해도 반응을 보이지 않을 것이다. 특정 쓰레드가 자원을 독점하지 않도록 하려면 쓰레드 루프안에 Thread.Sleep()이 들어가야 한다. 이제 각각의 Do 함수를 다음과 같이 수정하고 다시 컴파일하고 실행해 보도록 하자.
private void DoPrinting()
    {
      Console.WriteLine("인쇄 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
        Console.Write("p|");
        Thread.Sleep(1);
      }

      Console.WriteLine("인쇄 완료");
    }

    private void DoSpelling()
    {
      Console.WriteLine("철자 검사 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
        Console.Write("c|");
        Thread.Sleep(1);
      }
     
      Console.WriteLine("철자 검사 완료");
    }

    private void DoSaving()
    {
      Console.WriteLine("저장 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++)
      {
        Console.Write("s|");
        Thread.Sleep(1);
      }
      Console.WriteLine("저장 완료");
    }
실행결과를 보면 "c", "p", "s"가 사이 좋게 번갈아가며 찍히는 것을 알 수 있을 것이다. 위 예제에서 탐욕 쓰레드를 언급하면서 하나의 쓰레드에 대해서만 Highest를 주었다. 만약 세 개의 쓰레드가 모두 Normal인 경우에는 탐욕 쓰레드가 있을 수 없는 걸까? 확인해보기 위해서 Do 함수에서 Thread.Sleep(1)을 모두 지워버리자. 물론, 우선 순위를 설정하는 부분이 있다면 그 부분도 지우도록 한다. DoSpelling() 함수를 다음과 같이 수정한다.
private void DoSpelling()
    {
      Console.WriteLine("철자 검사 시작");
      for ( int LoopCtr = 0; LoopCtr < 1000000; LoopCtr++)
      {
        Console.Write("c|");
      }
      Console.WriteLine("철자 검사 완료");
    }
실행 결과를 보면 불행히도 쓰레드의 자원 독점은 일어나지 않는다. 각각의 쓰레드가 여전히 번갈아가며 실행되는 것을 알 수 있다. 이것은 타임 슬라이싱 때문에 그렇다. 타임 슬라이싱(Time Slicing) CPU가 하나인 시스템에서 쓰레드는 한 번에 하나의 코드 만을 실행할 수 있다고 말한 것을 기억하는가. 그렇다면 운영체제는 새로운 쓰레드를 스케줄링하기 위해 어떻게 쓰레드에서 CPU의 제어권을 가져올 수 있는가? 운영체제도 결국 코드에 불과한데, 어떻게 해서 제어권을 가져올 수 있을까? 그 정답은 타임 슬라이싱에 있다. NT와 같은 운영체제는 컴퓨터의 하드웨어에서 일정 시간 주기로 인터럽트를 발생시키도록 타이머를 지정한다. 스케줄러는 쓰레드에게 적절한 시간을 할당한다. 다음 인터럽트가 발생할 때까지 쓰레드는 CPU를 독점적으로 사용하게 된다. 인터럽트가 발생하면 쓰레드는 스케줄러에게 제어권을 넘겨준다. 쓰레드가 제어권을 넘겨주면 스케줄러는 같은 우선 순위를 갖고 있는 다른 쓰레드에게 제어권을 넘겨준다. 만약 같은 우선 순위를 갖고 있는 다른 쓰레드가 없다면 같은 쓰레드가 다시 실행 시간을 할당 받고 CPU를 독점적으로 사용하도록 한다. 따라서 위 예제에서 같은 우선 순위를 갖고 있는 경우에는 자원의 독점적인 사용이 일어나지 않는다. 마찬가지 이유로 하나의 쓰레드의 우선 순위가 높은 경우에는 하나의 쓰레드가 계속해서 실행된다. 그러나 CLR이 NT가 아닌 다른 운영체제에서 호스트된다면 이와 같은 동작은 보장할 수 없다. 마지막으로 Thread 클래스에서 관심을 가질만한 멤버들을 표에 정리해 두었다.
정적 멤버설명
CurrentContext현재 실행중인 쓰레드 컨텍스트를 가져온다.
CurrentThread현재 실행중인 쓰레드의 참조를 가져온다.
GetData()
SetData()
쓰레드의 현재 도메인에 대해서 실행중인 쓰레드의 특정 슬롯의 값을 설정하거나 가져온다.
GetDomain()
GetDomainID()
현재 쓰레드가 실행중인 도메인에 대한 참조를 가져온다.
Sleep()현재 실행중인 쓰레드를 일정 시간 동안 중지시킨다.
[표2] Thread 클래스의 정적 멤버
인스턴스 멤버설명
IsAlive쓰레드가 시작된 이후로 살아있는지를 알려준다.
IsBackground쓰레드가 백그라운드 쓰레드인지 아닌지를 설정하거나 알려준다(ThreadState.Background).
Name쓰레드의 이름을 설정하거나 가져올 수 있다.
PriorityThreadPriority 열거형에 정의된 쓰레드 우선 순위를 정의하거나 알려준다.
ThreadStateThreadState 열거형에 정의된 쓰레드의 상태를 알려주는 읽기 전용 프로퍼티
Abort()쓰레드를 죽일 때 사용한다. 이 메소드를 사용하면 ThreadAbortException 예외가 발생한다(ThreadState.Aborted, ThreadState.AbortRequested).
Interrupt()현재 쓰레드를 중지시킨다.
Join()현재 쓰레드가 완료될 때까지 기다린다(ThreadState.WaitSleepJoin).
Suspend()쓰레드를 잠시 대기상태로 만든다(ThreadState.Suspended, ThreadState.SuspendRequested).
Resume()대기 상태로 만든 쓰레드를 다시 활성상태로 만든다.
Start()ThreadStart에 위임한 쓰레드 실행을 시작한다(ThreadState.Unstarted, ThreadState.Running).
[표3] Thread 클래스의 인스턴스 멤버 IsAlive의 주의할 점은 이 쓰레드가 실행 가능한지, Sleep, Join 등에 의해 블록되어 있는지, 지금 실행되고 있는지를 알 수는 없다. 또한 쓰레드가 실행할 수 없는 상태와 종료된 상태를 구별하지는 못한다. 쓰레드의 상태를 알고자 하는 경우에는 ThreadState를 사용하도록 한다. Abort()에 대하여 설명했으나 실제로 멀티 쓰레드 응용 프로그램에서 쓰레드를 중지시킬 때는 Abort()를 사용하지 않고 Interrupt()를 사용하도록 한다. Suspend()를 사용하는 것은 바람직하지 않으며 대신에 Sleep()을 사용하도록 한다. 만약 입력 상태에서 쓰레드가 대기 상태가 되었다면 쓰레드를 Resume() 할 수 없게 된다. 이와 같이 수행이 보류된 스레드는 별도의 작업(Resume() 사용과 같이)이 없는 경우에는 다시 수행되지 않는다. 이러한 쓰레드를 보류된 쓰레드라 한다. 보류된 쓰레드와 블로킹된 쓰레드(Blocked Thread)의 차이점은 다음과 같다. 보류된 쓰레드는 별도의 작업이 없으면 더 이상 실행되지 않으며, 블로킹된 쓰레드는 프로그래머가 자발적으로 Sleep(), Join()과 같은 함수를 호출하여 특정 기간 동안 쓰레드의 수행을 보류하는 것이다. 따라서 지정한 기간이 지나거나(Sleep의 경우), 특정 쓰레드의 실행이 끝난 경우(Join의 경우)에 쓰레드는 다시 실행되게 된다. 요약 쓰레드 스케줄링과 쓰레드 우선순위에 대하여 알아보았다. 반드시 기억해야 할 것은 다음과 같다.
  • 쓰레드가 독점하지 않도록 하려면 쓰레드 루프에 Thread.Sleep() 등을 사용하여 적절히 제어권을 양보하도록 해야 한다.
  • 멀티 쓰레드 응용 프로그램은 단순히 쓰레드 우선 순위 제어만으로는 쓰레드의 실행 순서를 제어할 수 없다.
  • 코드를 실행하도록 하기 위해서 우선 순위를 제어하는 것은 잘못하고 있는 것이다. 따라서 멀티쓰레드를 제어하는 신뢰성있는 기법을 익히기 위해 이 강좌를 보거나 다른 참고문헌을 보는 것이 좋다.
다음 단계 다음에는 스케줄러, 멀티 프로세서 환경에서의 쓰레드와 쓰레드 친밀도(Thread Affinity)에 대해서 간단히 알아볼 것이다. 또한, 쓰레드의 예외 처리에 대해서 알아보고, 어떠한 문제점이 있는지 알아보도록 하자. 참고로 필자는 쓰레드에 대해서 모르기 때문에 글이 틀릴 수가 있다. 그러니 이상한 점이 있다면 주저말고 Email(traxacun@unitel.co.kr)을 보내주기 바란다.
TAG :
댓글 입력
자료실

최근 본 상품0