개발언어/C#

[C#] 스레드(Thread) 사용하기 - 생성, 일시 중단, 중단, 백그라운드 스레드

내꺼블로그 2024. 4. 18. 11:50

스레드(Thread)

스레드(Thread)란 프로세스 내부에서 수행되는 작업 단위를 의미하며, 프로세스는 하나 이상의 스레드를 가지고 있다.

스레드는 비동기식(asynchronous)으로 수행되어 동시에 처리되는 병렬성을 가지고 있다.

 


 

스레드 사용하기

C#을 사용하여 스레드를 사용하는 법에 대해 알아보자.

 

 

스레드 생성

스레드 사용을 위해 System.Threading 네임스페이스 추가한다.

 

 

 

Thread thr을 생성과 동시에 초기화해준다.

스레드 생성자의 매개변수는 ThreadStart의 대리자인 메서드이며 매개변수를 필요로 하는 메서드는 Start()메서드에 인자를 넣어 호출하면 된다.

대리자의 매개변수의 타입은 object로 한다(안그러면 에러남)

 

 

대리자의 매개변수가 없는 경우

이때는 인자 없이 Start()메서드를 호출해주면 된다.

Start() 메서드는 스레드를 실행하는 함수이다.

 

Start 메서드

 

스레드 생성자

 

예시 밖에도 스레드의 생성자 선언은 위와 같다.

(그래봤자 예시에서 스레드의 최대 스택 크기를 추가로 선언하는게 전부)

 

 

현재까지의 포스팅 내용을 토대로 다음의 코드를 작성해보았다.

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 100;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
            }
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
            }
        }
    }
}

 

 

 

코드 실행 결과는 다음과 같다.

메인스레드와 서브스레드가 동시에 실행되고 있는 상황

 

 


 

스레드 일시 중단(Thread.Sleep)

해당 스레드가 지정 시간 동안 중단되도록 하는 메서드

해당 스레드가 중단될 동안 다른 스레드는 여전히 작동한다.

 

 

 

- 서브스레드에만 Thread.Sleep 사용한 경우

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 10;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
            }
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
                Thread.Sleep(1000);
            }
        }
    }
}

 

 

서브스레드가 Thread.Sleep의 인자만큼 일시 중단된 모습을 볼 수 있다.

(반면 메인스레드는 Thread.Sleep를 사용하지 않아서 서브스레드가 중단될 동안 작동한 모습을 볼 수 있다.)

 

 

 

-메인스레드, 서브스레드에 Thread.Sleep 사용한 경우

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 10;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
                Thread.Sleep(1000);
            }
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
                Thread.Sleep(1000);
            }
        }
    }
}

 

메인 스레드, 서브 스레드 모두 Thread.Sleep 인자만큼 실행이 일시 중단된 모습을 볼 수 있다.

 

 

 

이런 애도 있더라.

얼핏 보기엔 Sleep과 별 차이가 없어 보여서 예제의 Sleep 자리에 SpinWait를 넣어보고 실행시켜보았다.

 

결과를 보니 분명 차이가 있긴 있는 모양

출력 형태가 꽤나 다르긴 한데 구체적으로 어떻게 동작하는지가 궁금한 1인

 

마이크로소프트에서는 SpinWait에 대해 다음과 같이 설명하는데 번역 문제 때문인지 읽어도 감이 오질 않는다...

혹시 명확하게 알고 계신 분은 댓글 부탁드립니닷🙏

 

 

+Thread.Sleep에서 알면 좋은(?) 사실

이렇다고 합니다(근데 사실 좀만 생각해보면 알 수 있을 듯)

 


 

스레드 중지(Thread.Join)

해당 스레드가 종료될 때까지 호출 스레드 차단

 

Join()의 반환형은 void인 반면, Join(Int32)과 Join(TimeSpan)의 반환형은 bool이다.

 

 

- Join()

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 10;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
                Thread.Sleep(1000);
            }
            thr.Join();
            Console.WriteLine("메인 종료");
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
                Thread.Sleep(1000);
            }
            Console.WriteLine("서브 종료");
        }
    }
}

 

서브 스레드 종료 이후 메인 스레드가 그 이후를 수행한 뒤 마무리 된 모습

 

 

- Join(Int32)

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 10;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
                Thread.Sleep(1000);
            }
            if (thr.Join(3000))
            {
                Console.WriteLine("서브 스레드 종료");
            }
            else
                Console.WriteLine("시간 초과");
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
                Thread.Sleep(1000);
            }
        }
    }
}

 

메인 스레드와 서브 스레드가 동시에 끝난 뒤 thr.Join(3000)이 호출됨

-> Join이 호출된 시점엔 이미 서브스레드가 종료되었기 때문에 true를 반환하여 다음 문장을 출력

 

메인 스레드의 Thread.Sleep의 인자 값을 낮춰 thr.Join(3000)이 더 빨리 호출되도록 변경

 

thr.Join이 호출된 이후에도 서브 스레드가 계속 남아있었기 때문에 false를 반환하여 '시간 초과' 문구가 출력된 것을 확인할 수 있다.

 

 

Abort()

스레드 중지하는 메서드로 Abort라는 메서드도 존재하는데 Join과 달리 함수 종료를 보장하지 않는다는 차이점이 있다.

(.NET에서 Abort를 사용하지 않는 버전이 꽤 되는 모양이다. 그냥 안쓴다고 생각해도 무방할 듯)

 

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thr = new Thread(Program.CountNumber0ToNum);
            int num = 10;
            thr.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("main count: {0}", i);
                Thread.Sleep(100);
            }
            thr.Abort();
            Console.WriteLine(":p");
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("thread count: {0}", i);
                Thread.Sleep(1000);
            }
        }
    }
}

 

이처럼 Abort를 사용하면 서브 스레드의 함수 실행이 끝나지 않았음에도 냅다 강제 종료된 뒤 Abort 이후 코드가 실행된다.

Join은 함수 종료 이후에 스레드를 중단, Abort는 함수 종료 관계없이 호출된 순간 스레드 중단

 


 

백그라운드 스레드

스레드는 백그라운드와 포그라운드 두 가지로도 나뉠 수 있다.

스레드 속성 중 하나

 

포그라운드 스레드는 종료되기 전까지 프로세스가 종료되지 않는 반면, 백그라운드 스레드는 프로세스가 종료되면 백그라운드 스레드 역시 그냥 종료된다는 의미

 

IsBackground를 true 또는 false로 설정하여 스레드를 백그라운드 스레드 또는 포그라운드 스레드로 설정할 수 있다.

 

using System;
using System.Threading;

namespace TestThread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread backgroundThread = new Thread(Program.CountNumber0ToNum);
            backgroundThread.IsBackground = true;
            int num = 10;
            backgroundThread.Start(num);
            for (int i = 0; i <= num; i++)
            {
                Console.WriteLine("foreground count: {0}", i);
                Thread.Sleep(500);
            }
            Console.WriteLine(":p");
        }

        static void CountNumber0ToNum(object num)
        {
            for(int i = 0; i <= (int)num; i++)
            {
                Console.WriteLine("background count: {0}", i);
                Thread.Sleep(1000);
            }
        }
    }
}

 

 

 

포그라운드 스레드의 작동이 끝남에 따라 프로세스가 종료되고, 이에 함수가 종료되지 않았던 백그라운드 스레드 역시 프로세스가 종료됨에 따라 그대로 종료되는 것을 볼 수 있다.

 

 


 

참고사이트

https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.thread?view=netframework-4.7.2&source=recommendations

 

Thread 클래스 (System.Threading)

스레드를 만들고 제어하며, 해당 속성을 설정하고, 상태를 가져옵니다.

learn.microsoft.com

 

https://frozenpond.tistory.com/23

 

[c#] 스레드(Thread)사용법 및 예제

개요 1. Thread 2. lock 3. BeginInvoke(비동기, 스레드풀) 4. BeginInvoke2(비동기 리턴값) 5. BeginInvoke3(콜백) 6. BeginInvoke4(BeginInvoke 예제) 이번 게시글에서는 스레드에 대해 정리합니다. 들어가기 앞서 프로세

frozenpond.tistory.com

https://wergia.tistory.com/187

 

[C#] Thread - 여러 작업을 동시 처리하기

Thread - 여러 작업을 동시 처리하기 일반적으로 우리가 사용하는 운영체제(Operation System, OS)은 멀티 태스크를 지원한다. 그 덕분에 우리는 구글에서 자료를 찾으면서, 유튜브에서 강좌를 듣고, 동

wergia.tistory.com