C#/네트워크

[C#][서버] 기본 멀티쓰레드 프로그래밍

goliot 2024. 5. 30. 14:55
반응형

기본 쓰레드 생성해보기

using System;
using System.Threading;

namespace ServerCore
{
    class Program
    {
        static void MainThread()
        {
            Console.WriteLine("Hello, Thread!");
        }

        static void Main(string[] args)
        {
            Thread t = new Thread(MainThread);
            t.Start();

            Console.WriteLine("Hello, World!");
        }
    }
}

기본 쓰레드 생성 문법

쓰레드 관련 명령어

  • .Join() - 해당 쓰레드를 기다린 후 다음 줄을 실행하겠다는 의미
  • .IsBackground = t/f - 해당 쓰레드를 백그라운드로 실행할 지 여부를 결정
using System;
using System.Threading;

namespace ServerCore
{
    class Program
    {
        static void MainThread()
        {
            Console.WriteLine("Hello, Thread!");
        }

        static void Main(string[] args)
        {
            Thread t = new Thread(MainThread);
            t.IsBackground = true; //백그라운드로 실행할 지 여부 결정
            t.Start();
            Console.WriteLine("Waiting for Thread");
            t.Join();//t가 끝날때까지 기다렸다가, 다음 줄을 실행하겠다
            Console.WriteLine("Hello, World!");
        }
    }
}

쓰레드 풀링

static void MainThread(object state)
{
    for(int i=0; i<5; i++)
        Console.WriteLine("Hello, Thread!");
}

static void Main(string[] args)
{
    ThreadPool.QueueUserWorkItem(MainThread) //스레드 작업 할당

    while(true) {}
}
  • ThreadPool을 이용해 작업 할당, 쓰레드 최대/최소 개수 설정 등이 가능
    • 이걸 통해 효율적인 쓰레드 관리가 가능
    • 어느 하나가 무한루프라면, 영영 돌아오지 않아서 한 자리를 차지하고 있음_
      • 그러므로 풀링은 짧은 쓰레드에 사용하는 것이 좋음
  • Thread t = new Thread(); 를 쓰지 않아도, 이미 쓰레드가 생성된 상태로 진행됨
    • 일이 끝나면, 대기소로 돌아감
  • 즉, ThreadPool은 쓰레드의 인력 사무소_
    • Thread t -> 이건 직접 알바 공고를 올려서 고용하는 것
    • static void MainThread(object state) { for(int i=0; i<5; i++) Console.WriteLine("Hello, Thread!"); } static void Main(string[] args) { ThreadPool.SetMinThreads(1, 1); ThreadPool.SetMaxThreads(5, 5); for (int i = 0; i < 5; i++) ThreadPool.QueueUserWorkItem((obj) => { while (true) { } }); ThreadPool.QueueUserWorkItem(MainThread); while (true) { } }
  • 쓰레드 최대를 5로 설정하고, 5개의 무한루프를 생성함
    • 그러면 최대 개수가 꽉 차서, MainThread가 실행되지 않음
    • 즉, 오래걸리는 쓰레드를 풀링으로 붙잡아두지 말자Thread
    • 자리를 먹지 않게 쓰레드 만들기
static void MainThread(object state) 
{ 
    for(int i=0; i<5; i++) Console.WriteLine("Hello, Thread!"); 
}

static void Main(string[] args)
{
    ThreadPool.SetMinThreads(1, 1);
    ThreadPool.SetMaxThreads(5, 5);

    for (int i = 0; i < 5; i++)
    {
        Task t = new Task(() => { while (true) { } }, TaskCreationOptions.LongRunning);
        t.Start();
    }

    ThreadPool.QueueUserWorkItem(MainThread);

    while (true) { }
}
  • LongRunning 옵션을 설정하면, 미리 이건 오래걸린다고 선언을 하는 것
    • ThreadPool의 좌석을 먹지 않음
      • 아래 출력 사진 참고

컴파일러 최적화

static bool _stop = false;

static void ThreadMain()
{
    Console.WriteLine("쓰레드 시작");

    while(_stop == false)
    {
        //누군가 stop 신호를 주기를 기다림
    }

    Console.WriteLine("쓰레드 종료");
}

static void Main(string[] args)
{
    Task t = new Task(ThreadMain);
    t.Start();

    Thread.Sleep(1000);

    _stop = true;

    Console.WriteLine("Stop 호출");
    Console.WriteLine("종료 대기중");

    t.Wait();

    Console.WriteLine("종료 성공");
}
  • 이 코드는 디버그 모드에서는 예상한대로 돌아간다.
    • 쓰레드가 실행 되고, 1초 뒤에 stop신호가 전해져 쓰레드가 정상적으로 종료됨
  • 하지만 Release 모드에서는?
    • 실제 배포를 진행할 때, Release모드로 진행함
      • 그러면 온갖 최적화가 들어가서 프로그램이 훨씬 빨라진다

해당 부분이 평소에는 Debug로 되어 있고, 그것이 평소 개발 환경이다.

  • Release 모드에서 컴파일러가, 코드를 다음과 같이 맘대로 바꿔버린다.
    • ThreadMain의 while 부분
      if(_stop == false)
      	while(true) {} 
      //주인님이 코드를 멍청하게 짰네? 이게 더 빠르니까 이렇게 실행해야겠다.
  • 이렇게 되면, _stop이 바뀌어도 캐치하지 못하게 되어 무한루프에 빠진다.
  • _stop 선언시, volatile static bool _stop = false; 처럼 volatile 키워드를 추가한다면
    • 이 변수 관련된 것은 최적화 하지 말아달라 라는 의미가 된다 -> 원래 의도한 대로 코드가 돌아감
      • 하지만 권장되지 않는 방법임
      • 메모리 배리어나, 락, 아토믹 같은 다른 옵션을 배워서 그걸 쓰자
반응형

'C# > 네트워크' 카테고리의 다른 글

[C#][서버] Lock 기초  (0) 2024.06.02
[C#][서버] Interlocked  (0) 2024.06.01
[C#][서버] 메모리 배리어  (0) 2024.06.01
[C#][서버] 캐시 이론  (0) 2024.05.31
[C#][서버] 서버 개론, 멀티쓰레드 개론  (0) 2024.05.30