스레드 재사용 (ThreadPool)

안녕하세요. C# 멀티스레드 프로그래밍에서 스레드 재사용을 직접 구현하려고 했는데, 구글링해보니 대부분 스레드 재사용에는 ThreadPool 클래스를 사용하더라구요. 좀 더 찾아보니 스레드에서 suspend 메서드는 deprecated 되었고(Thread.Suspend 메서드 (System.Threading) | Microsoft Learn), 스레드는 한 번 생성해서 중단 없이 실행하는 식으로 사용하는 것 같았습니다.

얕은 제 지식으로는 하나의 스레드로 둘 이상의 메서드를 실행시키려면, 전략패턴으로 객체에서 (스레드가 실행하고 있는)메서드를 동적으로 교체해주는 방법뿐이였습니다. 재사용보다는 교체죠…

정리하자면 질문은
“ThreadPool 클래스를 사용하지 않고 C#에서 스레드를 재사용하는 방법이 있나요?”
입니다.

  • 추가
    보통 이런 '우문’에는 “왜 ThreadPool 클래스를 사용하지 않으려고 하는지?” 라는 '현답’이 달릴 것 같아 추가합니다.
    ThreadPool 클래스에서 제공하는 메서드, 프로퍼티가 충분하지 않다고 느꼈습니다. 예를 들어 현재 작업 중인 스레드와 스레드가 처리하는 메서드를 확인할 수가 없었습니다. 결국 따로 ThreadPool에 넣기 전 자료구조를 직접 구현해서 확인했는데, 이럴바에 직접 구현하는게 더 편하겠다 생각이 들었습니다…
3개의 좋아요

그냥…그런 기능을 하는 객체를 만드시면 될 거 같습니다.
굳이 그렇게하려는 이유는 잘모르겠지만요.

앞서 언급된 tap방식의 스레드풀은 짧은 동작에 대해 미리 생성해둔 스레드를 재사용하며 일종의 spin시켜주는 역할이며 장기간 동작하는 스레드에 적합하지 않은 모델입니다.
물론 task 동작 시 longrunning 플래그를 사용하면 되긴하는데…

왜 이렇게하냐면, 모르신다는 가정하게 설명드리면

스레드 풀은 닷넷런타임이 시작되면서 스레드를 기본으로 5개를 가지고 시작하며(물론 코드로 조절가능) 짧게 끝나는 스레드에 대해 또 스레드를 생성하는 무거운 행위를 하지말고 미리 만들어 둔걸 써서 오버헤드를 절약하기 위함입니다.

그런데 여기서 하나를 장기간 점유해버리면 스레드풀에서 짧게 스핀되어야하는 자원중 하나가 점유되는 것이므로 스레드풀은 현재 갖고 있는 스레드양 보다 많은 스레드가 필요할 경우 스레드를 만들어내게 되고 또 일정기간 휴지기에 들어가면 스레드를 제거하기 시작합니다. 하나를 점유하면 그 하나만큼 계속 생성하고 제거할일이 생길 수도 있다는 것입니다.

따라서 메서드를 2개 돌리고 싶으시면 task를 두 번 사용하시는게 맞는거같고, 그게 오래 돌아가는 스레드여야한다면 tap말고 레거시 thread 클래스를 이용한 스레드풀을 직접 구현해주시면 될거 같습니다 (longrunning 플레그 사용하시거나요)

4개의 좋아요

아…이유를 달아주셨군요… 그럼 제 짧은 의견으론…커스터마이징하게 구현하시는게 맞을 것 같습니다.

2개의 좋아요

상세한 답변 감사합니다. longrunning 플래그도 알아봐야겠네요.
‘Thread 클래스를 이용한 스레드풀을 직접 구현’ 할 때 결국 (spin을 제외한) 스레드 재사용은 안되는 게 맞나요? 그러니까 작업마다 스레드를 매번 생성하고, 끝나면 제거해야 하나요?

2개의 좋아요

Thread 클래스를 이용하시면 일단 스레드를 멈출 때 Abort 시켜야하고 Abort된 스레드는 다시 사용 못하고 새로 생성해야하는 것으로 알고 있습니다. 이 부분은 Thread를 마지막으로 쓴게 오래전이라 잘은 기억이 안나네요…

아무튼…TAP 방식의 Task를 사용하는 스레딩은 .NET Framework 4.0에 도입되었고(async await 키워드는 .NET Framework 4.5), ThreadPool은 .NET Framework 2.0 인가…? 그 때 도입된 것으로 알고 있습니다. 그때는 좀 긴~ 코드로 스레드풀을 이용했어야했던거 같은데…

이 말씀을 드리는 이유는 Thread 클래스로 만들어낸 스레드는 ThreadPool과 무관한 독립된 스레드라는 것입니다.
ThreadPool 정적메서드 중에 현재 Thread가 ThreadPool의 스레드인지 검사하는 메서드도 있습니다.
예전에 테스트해봤던 기억으로는 Thread 클래스를 이용해 생성된 스레드는 ThreadPool안에 없었고, Task의 스레드는 ThreadPool 안에 있었습니다. LongRunning 플레그를 준 Task의 경우에도 ThreadPool과 무관하게 독립적 으로 동작했습니다. Thread ID를 체크하는 방식이니 테스트 해보시면 좋을 것 같습니다. (테스트하시는데 얼마 안 걸립니다.)


굳이 재사용처럼 쓰시려면, 말씀하신 것처럼 Suspend를 걸고 바꾸는 방식이 있을 것도 같습니다. 해본 적이 없어서 잘은 모르겠지만…

위에 언급한대로 제 의견 정리는, ‘스레드에 동작하는 메서드를 교체하는 개념이면 새로 생성하시는 게 맞다’ 입니다.

명시적인 코딩에도 그게 맞을 것 같습니다. C#은 OOP언어처럼 사용할 수 있다 보니 객체의 책임이라는 게 중요하게 고려되는 부분인데 메서드는 일종의 기능에 해당하고, 그 기능을 계속 바꿔가면서 사용한다면 네이밍 바뀌어야 맞을 것 같다는 제 의견입니다.

3개의 좋아요

아…메서드가 아니고 속성이었네요.

Thread.IsThreadPoolThread Property (System.Threading) | Microsoft Docs

2개의 좋아요