Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-16 04:51
관리 메뉴

nomad-programmer

[Programming/C#] 스레드 (Thread) 본문

Programming/C#

[Programming/C#] 스레드 (Thread)

scii 2020. 9. 28. 23:00

프로세스란?

프로세스는 실행 파일이 실행되어 메모리에 적재된 인스턴스이다.
가령 work.exe가 실행 파일이라면, 이 실행 파일을 실행한 것이 프로세스이다. 또한 프로세스는 반드시 하나 이상의 스레드(Thread)로 구성된다. 

스레드란?

스레드는 운영체제가 CPU 시간을 할당하는 기본 단위인데, 프로세스가 밧줄이라면 스레드는 밧줄을 이루는 실이라고 할 수 있다. 

멀티 스레드의 장점

1. 사용자 대화형 프로그램에서 멀티 스레드를 이용하면 응답성을 높일 수 있다는 점을 꼽을 수 있다. 예를 들어 제작한 프로그램이 파일을 복사하는데, 복사할 파일이 너무 커서 소요 시간이 30분 정도 걸린다고 해보자. 이 때 프로그램을 단일 스레드로 만든다면 프로그램이 파일을 복사하는 동안 사용자가 취소 명령을 내리고 싶어도 프로그램이 사용자에게 반응하지 않으므로 복사를 취소할 수 없다. 작업관리자를 이용해서 강제로 프로세스를 종료시켜야 한다. 만약 이 프로그램에 사용자와의 대화를 위한 스레드를 하나 더 추가한다면 파일 복사를 하면서도 사용자로부터 명령을 입력받도록 할수 있다.

2. 멀티 프로세스 방식에 비해 멀티 스레드 방식이 자원 공유가 쉽다는 것이다. 멀티 프로세스는 GUI가 없는 웹 서버 같은 서버용 애플리케이션에서 많이 취하는 구조인데, 프로세스끼리 데이터를 교환하려면 소켓이나 공유 메모리 같은 IPC(Inter Process Communication)를 이용해야 한다. 반면에 멀티 스레드 방식에서는 그저 스레드끼리 코드 내의 변수를 같이 사용하는 것만으로도 데이터 교환을 할 수 있다.

3. 경제성이다. 프로세스를 띄우기 위해 메모리와 자원을 할당하는 작업은 비용이 비싼데, 스레드를 띄울 때는 이미 프로세스에 할당된 메모리와 자원을 그대로 사용하므로 메모리와 자원을 할당하는 비용을 지불하지 않아도 된다.

멀티 스레드의 단점

1. 멀티 스레드 구조의 소프트웨어는 구현하기가 까다롭다. 테스트 역시 쉽지 않은데다 멀티 스레드 기반의 소프트웨어 디버깅은 정말 힘들다.

2. 멀티 프로세스 기반의 소프트웨어는 여러 개의 자식 프로세스 중 하나에 문제가 생기면 그 자식 프로세스 하나가 죽는 것 이상으로는 영향이 확산되지 않지만, 멀티 스레드 기반의 소프트웨어에서는 자식 스레드 중 하나에 문제가 생기면 전체 프로세스가 영향을 받게 된다.

3. 스레드를 너무 많이 사용하면 오히려 성능이 저하된다. 스레드가 CPU를 사용하기 위해서는 작업간 전환(Context Swtiching)을 해야 하는데, 이 작업간 전환이 적잖은 비용을 소모한다. 많은 스레드가 너무 자주 작업간 전환을 수행하다 보면 애플리케이션이 실제로 일을 하는 시간에 비해 작업간 전환에 사용하는 시간이 커지기 때문에 성능이 저하된다.


.NET 프레임워크는 스레드를 나타내는 클래스로 System.Threading.Thread를 제공한다. 이 클래스를 사용하는 방법은 아래와 같다.

  1. Thread의 인스턴스 생성. 이 때 생성자의 매개 변수로 스레드가 실행할 메소드를 매개 변수로 넘긴다.
  2. Thread.Start() 메소드를 호출하여 스레드를 시작한다.
  3. Thread.Join() 메소드를 호출하여 스레드가 끝날 때까지 기다린다.
static void DoSomething()
{
    for(int i=0; i<5; i++)
    {
        Console.WriteLine("DoSomething: {0}", i);
    }
}

static void Main(string[] args)
{
    // Thread의 인스턴스 생성
    Thread t1 = new Thread(new ThreadStart(DoSeomething));
    
    // 스레드 시작
    t1.Start();
    
    // 스레드 종료 대기
    t1.Join();
}

위 코드에서 실제 스레드가 메모리에 적재되는 시점은 t1.Start() 메소드를 호출했을 때이다. Thread 클래스의 인스턴스는 "준비"만 해둘 뿐이다. t1.Start() 메소드가 호출되고 나면, CLR은 스레드를 실제로 생성하여 DoSomething() 메소드를 호출한다. t1.Join() 메소드는 블록되어 있다가 t1 스레드의 실행이 끝나면 반환되어 다음 코드를 실행할 수 있게 된다.

Join 이라는 메소드의 이름은 스레드의 종료 대기와 상관이 없어 보이는 이름이라고 생각할 수 있다. Join은 영어로 "합류하다"라는 뜻이다. 스레드를 하나 실행하면 밧줄에서 실이 한 갈래 빠져 나왔다가 Join() 메소드가 반환되는 시점에서 이 실(스레드)이 다시 밧줄(프로세스)로 "합류"한다고 생각하면 된다.

Thread 예제

using System;
using System.Threading;

namespace CSharpExample
{
    internal class MainApp
    {
        static void DoSomething()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"DoSomething: {i}");
                // Sleep() 메소드는 다른 스레드도 CPU를 사용할 수 있도록 CPU 점유를 내려 놓는다.
                // 매개 변수는 밀리초 단위이다.
                Thread.Sleep(10);
            }
        }
        
        static int Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(DoSomething));

            Console.WriteLine("Starting thread...");
            t1.Start();

            // t1 스레드의 DoSomething() 메소드가 실행되는 동시에 
            // 메인 스레드의 이 반복문도 실행된다.
            for(int i=0; i<5; i++)
            {
                Console.WriteLine($"Main: {i}");
                Thread.Sleep(10);
            }

            Console.WriteLine("Waiting until thread stops...");
            t1.Join();

            Console.WriteLine("Finished");

            return 0;
        }
    }
}


/* 결과

Starting thread...
Main: 0
DoSomething: 0
DoSomething: 1
Main: 1
DoSomething: 2
Main: 2
DoSomething: 3
Main: 3
DoSomething: 4
Main: 4
Waiting until thread stops...
Finished

*/

Thread.Sleep() 메소드는 다른 스레드도 CPU를 사용할 수 있도록 CPU 점유를 내려 놓는다.

Comments