25.09.30 개발일지 / C# 4(1) (Chapter19)

2025. 11. 11. 10:59·LMS 7/개발일지

Chapter19. 스레드와 테스크

1. 프로세스와 스레드

1) 프로세스

> 프로세스란 작성한 코드의 데이터가 메모리에 올라 실행되는 단위

> 최근 아두이노를 활용한 프로젝트에서 노트북 1대로 각각 인증용 프로그램, 서버, 클라이언트를 모두 실행했는데 이 경우 프로세스를 3개 사용한 것이라 볼 수 있다

2) 스레드

> 스레드는 프로세스 내부의 개념으로써 프로세스 안에 여러 개의 스레드가 존재할 수 있는 것이 특징이다

> 여태까지 사용했던 Main이 대표적인 스레드라고 볼 수 있고 일반적으로 이것을 메인 스레드라고 부른다

> 메인 스레드 외에 다른 스레드를 생성했다면 메인 스레드와 여러 개의 서브 스레드가 있는 것

▲ 메인 스레드 하나만 있는 경우(Main)

 

▲ 메인 스레드 뿐만 아니라 여러 개의 서브 스레드가 있는 경우(Main, Sub1, Sub2 ... Sub N)

 

2. 스레드 시작

1) 사용법

static void DoSomething(){~} // 스레드 실행 메서드 정의
Thread t1 = new Thread(new ThreadStart(DoSomething)); // 스레드 객체 생성 // 사용할 메서드와 함께 대리자 객체를 인수로 넘김
t1.Start(); // 스레드 시작
t1.Join(); // 스레드 종료대기

▲ 순서

: 스레드 실행 메서드 정의 -> 스레드 객체 생성 -> 스레드 실행 메서드와 함께 대리자 객체를 인수로 넘김 -> 스레드 시작 -> 스레드 종료대기

 

3. 스레드 종료

1) Abort()

> 스레드를 임의종료시키기 위해서는 Abort() 메서드를 호출한다.

Thread t1 = new Thread(new ThreadStart(DoSomething)); // 스레드 객체 생성

t1.Start(); // 스레드 시작
t1.Abort(); // 스레드 임의 종료
t1.Join(); // 스레드 종료대기

> 스레드 임의종료의 경우에 ThreadAbortException 이라는 예외를 던지는데 이 예외를 처리할 수 있다면 처리를 마무리하고, 아닌 경우에는 스레드를 종료시킴

 

2) Interrupt()

> Abort와 가장 큰 차이점은 Interrupt()는 스레드가 동작 중(Running)일 때는 종료시키지 않고, 쉬는 중(WaitSleepJoin)일 때만 종료시킨다는 점임.

Thread t1 = new Thread(new ThreadStart(DoSomething)); // 스레드 객체 생성

t1.Start(); // 스레드 시작
t1.Interrupt(); // 스레드 임의 종료
t1.Join(); // 스레드 종료대기

 

4. 스레드 상태

> 스레드는 여러 상태를 가질 수 있음

- Running : 스레드 동작 중

- WaitSleepJoin : 스레드 블록 상태(Thread.Sleep(), Thread.Join() 호출 시 이 상태가 됨)

- Aborted : 스레드 취소 상태(이후 Stopped 상태로 전환되어 완전히 중지됨)

- Stopped : 스레드 중단 상태

static void Main()
{
    Thread t1 = new Thread(new ThreadStart(DoSomething)); 

    t1.Start(); // t1 스레드는 Running 상태

    Console.WriteLine("스레드 실행 중");

    // 호출로 메인 스레드는 WaitSleepJoin 상태, 이후 t1 스레드가 Stopped 상태가 되면 다시 메인 스레드는 Runnig 상태가 됨
    t1.Join();
}

 

5. 스레드 간 동기화

> 스레드의 가장 큰 장점이 같은 프로세스 내 스레드끼리는 자원(전역변수)을 공유할 수 있다는 점인데, 이는 동시에 단점이 될 수 있다

> 단점은 한 스레드가 이 자원에 접근했을 때 다른 스레드가 접근을 하게되어 이 자원을 멋대로 바꿔 놓는다면 의도한 행동과 다르게 잘못된 결과를 불러올 수 있음

> 따라서 이러한 단점을 해결할 수 있는 것이 "동기화"이고, 구체적으로는 한 스레드가 자원을 사용할 때에는 다른 스레드에서 접근할 수 없도록 하는 것을 의미한다

 

1) lock

> lock 키워드를 통해서 원하는 로직을 여러 스레드가 함부로 접근할 수 없도록 만들 수 있다

private readonly object thisLock = new object(); // lock용 매개변수 생성

public void func()
{
    // 매개변수를 넣어 lock 키워드를 사용
    // 이제부터 아래 블록은 한 스레드가 접근시 다른 스레드 접근 불가
    lock(thisLock) 
    {
        ~
    }
}

 

2) Monitor(Enter, Exit)

> lock과 다른 점은 수동 제거를 해줘야 한다는 점

private readonly object thisLock = new object(); // lock용 매개변수 생성

public void func()
{
    Monitor.Enter(thisLock); // lock처럼 사용
    try{};
    finally{ Monitor.Exit(thisLock); } // lock과 달리 없애줘야 함
}

 

3)Monitor.Wait, Monitor.Pulse

> 모든 스레드들은 Ready Queue에 입력된 후에 lock 객체를 얻으며 개별적인 작업을 하는 구조로 되어 있다.

> Monitor.Wait을 호출하게 되면 스레드를 WaitSleepJoin 상태로 변경하고 가지고 있던 lock 객체를 내려놓은 후 Waiting Queue에 입력되게 됨.

> 이 상태는 다른 스레드가 lock 객체를 획득할 수 있는 상태가 되어 다른 스레드가 접근을 할 수 있는 상태가 된다

> 이후 위 스레드가 Monitor.Pulse를 호출하게 되면 Waiting Queue의 가장 첫번째 위치의 스레드를 꺼내어 Ready Queue에 입력하게 됨

> 맨 위의 Ready Queue 작동 방식에 따라 스레드는 작업을 시작한다.

private readonly object thisLock = new object(); // lock용 매개변수 생성
bool locked = false; // 스레드의 WaitSleepJoin 상태를 추적할 변수

public void func()
{
    lock(thisLock)
    {
        // 2. 다른 스레드는 변경된 locked 상태 조건 때문에 Monitor.Wait에 걸려 WaitSleepJoin 상태가 됨
        // 5. 다시 Ready Queue에 입력된 스레드는 다른 스레드들과 함께 동작한다.
        while(locked == true) Monitor.Wait(thisLock);
        
        // 1. 첫 스레드는 While 조건을 건너뛰어 locked를 true로 변경한다
        locked = true;

        // yourLogic

        // 3. 첫 스레드가 위의 로직(yourLogic)을 모두 수행한 뒤 다시 locked 상태 조건을 변경한다.
        locked = false;

        // 4. Monitor.Pulse를 통해 WaitSleepJoin 상태에 있던 스레드를 Ready Queue에 입력
        Monitor.Pulse(thisLock);
}

 

6. Task

> Task는 thread의 고성능 버전임

> thread의 문제는 사용되지 않는데도 리소스를 소비한다는 점이 가장 큼(성능 하락의 문제)

> 따라서 필요한 thread를 최소한으로 생성하되 비동기적으로 처리하는 것이 필요하다

> thread 수를 사전에 조절하여 갯수를 제한하는 ThradPool Queue에 Task는 내부적으로 등록되고, ThreadPool은 시스템 코어에 비례하여 스레드 수를 조정한다

> 결국 thread 수는 최적으로 맞추면서 동시에 비동기적으로 사용할 수 있기 때문에 Taks는 고성능 thread이다

 

1) 기본 사용법

Task my_task = new Task(someAction); // someAction은 반환형이 없는 메서드 또는 익명 메서드 또는 무명함수

my_task.Start(); // thread.Start()와 비슷함

// yourLogic

myTask.Wait(); // thread.Join()과 비슷함

▲ thread와 유사하게

var my_task = Task.Run(someAction); // Run은 생성과 시작을 동시에 함

// yourLogic

my_task.Wait();

▲ 생성과 시작을 동시에 할 수 있는 Run을 활용

 

2) Task<TResult>

> Task와 가장 큰 차이점은 비동기 실행 코드를 Action 대리자가 아닌 Func 대리자로 받는다는 점, 결과를 반환받을 수 있다는 점임

> 참고로 Action과 Func는 무명함수(람다식을 이용한 익명메서드를 따로 부르는 말)를 만드는 키워드임.

var my_task = Task.Run<List<int>>(() => // 람다식 사용하여 Task<TResult> 생성 및 시작
        {
            var result = new List<int>();
            for (int i = 1; i <= 5; i++)
            {
                result.Add(i * i);
            }
            return result;
        });

List<int> numbers = my_task.Result; // 결과를 반환받기

 

7. async / await

> 이들은 비동기화를 할 수 있는 새로운 키워드임

1) 기본문법

public static async Task MyMethod() // 메서드에 async 키워드가 붙는다
{
    // async 메서드에는 await이 필수로 붙는다. 
    // 없는 경우 동기적으로 실행되므로 async의 의미가 없음.
    await Task.Run(YourMethod);
}

> async 연산자가 붙은 메서드를 호출하게 되면, async 메서드는 블록을 순서대로 실행시키며 await을 찾게되고 만나게 되면 호출자에게 제어를 돌려주며 자신의 블록을 계속 실행시킨다.

> 제어권을 받은 호출자는 자신의 블록을 실행시킨다.

> 따라서 async와 await을 기준으로 호출자 블록과 asnyc 블록을 동시에 처리하게 되므로 비동기적임.

 

2) 사용법

using System;
using System.Threading.Tasks;

namespace Async
{
    class MainApp
    {
        async static private void MyMethod(int count)
        { // async 메서드 생성
            Console.WriteLine("async.Hello");

            await Task.Run(async () => // 람다식 이용한 await 키워드 사용
            {
                for (int i = 1; i < count; i++)
                {
                    Console.WriteLine($"yourCount : {i}");
                    await Task.Delay(100);
                }
            });

            Console.WriteLine("async.Bye");
        }

        static void Caller()
        { // async 메서드와 비동기적으로 사용될 메서드
            Console.WriteLine("Caller.Hello");

            MyMethod(7);

            for (int i = 0; i < 7; i++)
            {
                Console.WriteLine($"Caller.Bye : {i}");
            }
        }

        static void Main(string[] args)
        {
            Caller();

            Console.ReadLine();
        }
    }
}

> 근소하지만 어느정도 차이가 있음

'LMS 7 > 개발일지' 카테고리의 다른 글

25.10.02 개발일지 / C# 4(2) (Chapter20~22)  (0) 2025.11.11
25.10.01 개발일지 TCP/IP 스택  (0) 2025.11.11
25.09.30 개발일지 / C# 클래스  (0) 2025.11.11
25.09.29 개발일지 / C# 3 (Chapter13)  (0) 2025.11.11
25.09.29 개발일지 / C# 2(3) (Chapter11, Chapter12)  (0) 2025.11.11
'LMS 7/개발일지' 카테고리의 다른 글
  • 25.10.02 개발일지 / C# 4(2) (Chapter20~22)
  • 25.10.01 개발일지 TCP/IP 스택
  • 25.09.30 개발일지 / C# 클래스
  • 25.09.29 개발일지 / C# 3 (Chapter13)
m_Dev
m_Dev
  • m_Dev
    m_Dev
    m_Dev
  • 전체
    오늘
    어제
    • 분류 전체보기
      • MAIN STUDY
        • 정보보안
        • 빅데이터
        • 정보처리
        • 컴퓨터 구조
        • 기타
      • JOB
        • Study
        • Project
      • LMS 7
        • 개발일지
      • FRAMEWORK
        • Qt
        • MFC
        • Winform
        • WPF
        • MAUI
      • NETWORK
        • Study
        • Assignment
      • PYTHON
        • Set
        • Study
        • Assignment
        • Project
      • C
        • Set
        • Study
        • Assignment
        • Project
      • C++
        • Set
        • Study
        • Assignment
        • Project
      • C#
        • Set
        • Study
        • Assignment
        • Project
      • DATABASE
        • MySQL
        • SQLite
      • IDE
        • VisualStudioCode
        • VisualStudio
        • Pycharm
        • Colab
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
m_Dev
25.09.30 개발일지 / C# 4(1) (Chapter19)
상단으로

티스토리툴바