[C#] 36. 스레드(Thread)를 사용하는 방법, Thread.Sleep 함수 사용법


Study/C#  2021. 9. 24. 20:31

안녕하세요. 명월입니다.


이 글은 C#에서 스레드(Thread)를 사용하는 방법, Thread.Sleep 함수 사용법에 대한 글입니다.


우리가 프로그램을 작성해서 실행하면 소스의 순서대로 실행되는 게 기본 흐름입니다.

이것을 프로세스라고 표현합니다. 즉, 프로그램이 실행되서 종료할 때까지는 하나의 프로세스가 실행되는 것입니다.

using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 반복문 0부터 9까지
      for (var i = 0; i < 10; i++)
      {
        // 콘솔 출력
        Console.WriteLine("a = " + i);
      }
      // 반복문 0부터 9까지
      for (var i = 0; i < 10; i++)
      {
        // 콘솔 출력
        Console.WriteLine("b = " + i);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제는 0부터 9까지의 반복문을 두번 실행합니다.

당연히 a = 의 출력이 먼저 실행되고 종료가 되면, b = 의 출력이 다음에 실행이 됩니다. 이것이 하나의 프로세스입니다.


그런데, 사양에 따라서 이 두 for문을 병렬로 동시에 실행하고 싶을 때가 있습니다.

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 첫번째 스레드 생성
      var thread1 = new Thread(() =>
      {
        // 반복문 0부터 9까지
        for (var i = 0; i < 10; i++)
        {
          // 콘솔 출력
          Console.WriteLine("a = " + i);
        }
      });
      // 두번째 스레드 생성
      var thread2 = new Thread(() =>
      {
        // 반복문 0부터 9까지
        for (var i = 0; i < 10; i++)
        {
          // 콘솔 출력
          Console.WriteLine("b = " + i);
        }
      });
      // 첫번째 스레드 실행
      thread1.Start();
      // 두번째 스레드 실행
      thread2.Start();

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보시면 첫번째 스레드와 두번째 스레드가 동시에 실행되어서 콘솔에 무작위로 출력이 되는 것을 확인할 수 있습니다.

즉, 위 예제는 하나의 프로세스에 두개의 스레드가 작동을 한 것입니다. 세개의 병렬처리가 실행되는 것입니다.

예제를 보시면 "Press Any key..."가 먼저 출력이 되는 것을 볼 수 있는데, 이는 스레드를 실행하는 데, 약간의 딜레이가 발생하기 때문에 프로세스 처리가 먼저 출력이 되어 버린 것입니다.


스레드를 사용하는 방법은 Thread 클래스의 인스턴스를 생성하고 생성자 파라미터로는 반환값과 파라미터가 없는 델리게이트로 받습니다.

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 스레드에서 사용될 함수
    static void ThreadMethod1()
    {
      // 변수
      var sum = 0;
      // 반복문 0부터 99999까지
      for (var i = 0; i < 100000; i++)
      {
        // 변수에 더한다.
        sum += i;
      }
      // 콘솔 출력
      Console.WriteLine("Sum1 = " + sum);
    }
    // 스레드에서 사용될 함수
    static void ThreadMethod2()
    {
      // 변수
      var sum = 0;
      // 반복문 0부터 999까지
      for (var i = 0; i < 1000; i++)
      {
        // 변수에 더한다.
        sum += i;
      }
      Console.WriteLine("Sum2 = " + sum);
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 스레드 생성
      var thread1 = new Thread(ThreadMethod1);
      var thread2 = new Thread(ThreadMethod2);
      // 스레드 실행
      thread1.Start();
      thread2.Start();

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

그런데 이 Thread함수에는 변수를 전달할 수 없기 때문에, 위의 람다식을 사용하고 클로져(Closure) 기능을 이용해서 변수를 전달하는 방법을 자주 사용합니다.

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 변수
      var sum = 0;
      // 스래스 생성
      var thread1 = new Thread(() =>
      {
        // 반복문 0부터 999까지
        for (var i = 0; i < 1000; i++)
        {
          // 변수에 더한다.
          sum += i;
        }
      });
      // 스레드 시작
      thread1.Start();
      // 콘솔 출력
      Console.WriteLine("Sum = " + sum);

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

그런데 위 예제를 보면 Thread를 사용해서 for문으로 값을 더하였는데 결과는 0이 나왔습니다.

이유는 스레드가 종료될 때까지 기다리지 않고 프로세스가 먼저 실행되기 때문입니다. 즉, 콘솔이 출력될 당시의 값은 sum의 변수 값이 0이였습니다. 병렬로 실행되기 때문에 콘솔이 출력된 후에 sum의 변수 값이 변하겠네요.


병렬 처리를 한다면 성능이 빨라지겠지만 프로세스에서 스레드의 값을 제대로 받지 못하면 의미가 없습니다.

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 변수
      var sum = 0;
      // 스래스 생성
      var thread1 = new Thread(() =>
      {
        // 반복문 0부터 999까지
        for (var i = 0; i < 1000; i++)
        {
          // 변수에 더한다.
          sum += i;
        }
      });
      // 스레드 시작
      thread1.Start();
      // 스레드가 종료할 때까지 프로세스를 중지
      thread1.Join();
      // 콘솔 출력
      Console.WriteLine("Sum = " + sum);

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

thread1 변수에 Join 함수를 사용하면 프로세스에서 스레드가 종료할 때까지 대기하는 역할을 합니다. 즉, 여러개의 스레드를 사용한다면 Join 함수를 사용해서 프로세스와 동기화하게 되면 프로그램을 빠르게 처리할 수 있습니다.


또 사양에 따라서 Thread를 일부러 처리를 늦추게 만들 수도 있습니다.

using System;
using System.Threading;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 스래스 생성
      var thread1 = new Thread(() =>
      {
        // 반복문 0부터 9까지
        for (var i = 0; i < 10; i++)
        {
          // 현재 시간을 콘솔 출력
          Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ssss"));
          // 1초 대기
          Thread.Sleep(1000);
        }
      });
      // 스레드 시작
      thread1.Start();
      // 스레드가 종료할 때까지 프로세스를 중지
      thread1.Join();

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

Thread.Sleep() 함수를 통해서 스레드를 1초간 멈추는 처리를 합니다.Sleep 함수에서는 밀리초 단위로 설정이 가능하기 때문에 1000을 넣어야 1초가 됩니다.


스레드는 프로세스를 병렬 처리가 가능하게 하는 기능입니다. 적절하게 사용하게 되면 프로그램의 성능을 비약적으로 올릴 수 있습니다.

그러나 스레드라는 것도 하나의 리소스(자원)이기 때문에, 그 한계가 존재합니다. 즉, 스레드를 무한히 만든다고 해서 무한히 빨라지는 것이 아니고 .Net framework에서 스레드 리소스를 관리하기 때문에 어느 정도 이상 많아지게 되면 반대로 시스템이 느려집니다.

그래서 간단한 처리하는 계산식이라면 스레드를 생성하고 관리하는 부분으로 더 느려질 수 있기 때문에 오히려 스레드를 사용하지 않는 것이 빠릅니다.


프로그램에서 리소스를 관리(파일 관리, 통신 관리 등)하는 처리라던가 유저의 이벤트를 기다리는 처리 등으로 스레드를 많이 사용합니다.

이유는 프로세스 속도와 리소스 간의 처리 속도 간의 차이가 있기 때문에 이 갭을 줄이기 위해서 많이 사용하게 됩니다.


여기까지 C#에서 스레드(Thread)를 사용하는 방법, Thread.Sleep 함수 사용법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.