작업이 종료된 Thread의 객체는 가비지 컬렉터가 삭제 시키나요??

아래 코드에서 btnTempStart 버튼을 누르면 Thread 가 생성이 되면서 동작이 됩니다.

그 상태에서 btnTempStop 버튼을 누르면 flag 가 false가 되면서 Thread는 종료가 될텐데

이 때 Thread 객체 자체는 사라지지 않은 상태인데 생성과 종료를 계속해서 반복할 경우 Thread 객체가 지속적으로 생성이 되면서 메모리를 차지하는 것인가요??

아니면 GC 에 의해 나중에 사용하지 않는 객체는 없어 지고 새로 동작하는 것만 남는 것인가요??

      public partial class frmMain:Form 
	{        
		Thread TempThread;
		private bool flag = false ;

		public frmMain() 
		{
			InitializeComponent();
		}

      	private void btnTempStart_Click(object sender, EventArgs e)
      	{
          		TempThread = new Thread(new ThreadStart(test));
          		TempThread.IsBackground = true;
          		flag = true ;
          		TempThread.Start();
      	}

      	private void btnTempStop_Click(object sender, EventArgs e)
      	{
	            flag = false ;
      	}

      	private void test()
      	{
          		int t = 0 ;
          		while(flag) 
          		{
              		t ++;
              		Thread.Sleep(20);                 
          		}
      	}
}
2개의 좋아요

test()가 종료되었을 때 TempThread 객체는 GC 대상이 아닙니다.

   	private void btnTempStop_Click(object sender, EventArgs e)
    	{
            flag = false ;
            if (TempThread.ThreadState != ThreadState.Unstarted)
            {
                  TempThread.Join();
                  TempThread = null;
            }
    	}

이렇게 하면 test()가 종료된 후 GC 대상이 되겠네요.

2개의 좋아요

질문 코드에서 flag로 작업을 종료 하기 때문에 Join()은 불필요 하며,

없이도, 실행 해당 스레드가 종료 되면 GC 대상 아닌가요 ?

2개의 좋아요

계속 쌓이면서 메모리 차지하지 않고, 가장 끝놈 빼고 콜렉트 될 것 같은데요.
TempThread 변수로 참조하고 있는 마지막놈은 동작 끝나도 콜렉트 안될 것 같구요.
(전문가분이 답을 알려주시면 알림받기 위해 비전문가가 댓글 달아요)

2개의 좋아요
TempThread = new Thread(new ThreadStart(test));

제가 이해하고 있는 건 보통 새로운 객체가 할당되면서 이전 heap에 대한 참조가 사라지고 새로운 heap에 참조하고 있는 객체 말고는 GC가 되는 것으로 이해하고 있는데,

thread는 어떻게 되는지 생각을 못해봤던 것 같네요.

thread stack 영역이 생길텐데 만약 이 쪽에서 새로운 객체가 heap에 할당 되고 thread가 완료 됐을 때 처리가 어떻게 되려나요.

stack이 사라지면서 heap에 대한 구조도 참조가 끊기고 GC가 되는?? thread도 하나의 root path로 보려나… :thinking:

2개의 좋아요

TempThread 가 Form의 멤버로 설정이 되어 있어서 제가 만들어 놓은 코드에서는 GC 대상이 아닌듯 합니다.

이벤트 함수내에서 선언을 했다면 GC 대상이 되어서 당연히 삭제가 되겠지만 위의 코드 상황의 Form의 멤버이기 때문에 전역적인 상황이라 GC 가 안돼고 남아 있습니다.

궁금한 것은 GC 가 안된 상황에서 또 버튼을 누르면 새로운 객체로 만들어 질텐데 그렇게 될 경우 기존에 객체가 삭제가 안된 상황에서 새로운 객체가 누적되어 오버플로우 현상이 발생하는지 그게 궁금합니다.

2개의 좋아요

Thread 클래스의 인스턴스는 물리적 스레드에 대한 핸들러에 지나지 않습니다.

이 핸들러 자체는 닷넷 객체이기 때문에 일반적인 닷넷 객체의 생명주기를 따릅니다.

TempThread 는 폼 클래스의 인스턴스 멤버이기 때문에, frmMain 인스턴스가 사라질 때 함께 사라집니다.

이 것과 연결된 물리적 스레드의 상태 - 실행전, 실행 중, 실행 완료, 예외 발생 - 와 상관없이 말이죠.

3개의 좋아요

메모리 진단 도구로 보면 Thread가 날라간게 보이네요.

[종료 전]
image

[종료 후]
image

        private void Start(object sender, EventArgs e)
        {
            TempThread = new Thread(new ThreadStart(test));
            TempThread.IsBackground = true;
            flag = true;
            TempThread.Start();
        }

        private void Stop(object sender, EventArgs e)
        {
            flag = false;
            TempThread.Join();
            TempThread = null;
            GC.Collect();
        }
2개의 좋아요
private void Stop(object sender, EventArgs e)
{
            flag = false;
}

이렇게만 하면 Thread가 바이든 하지 않는다는 말인가요?

1개의 좋아요

flag = false; 를 하면 쓰레드는 당연히 종료가 되고 Stopped 상태가 됩니다.
궁금한 것은 tempThread 가 Form의 멤버이기 때문에 GC 가 안될테고 그 상태에서 다시 btnTempStart_Click 을 실행해서 쓰레드를 생성했을 경우 메모리 상에서 쓰레드 객체가 어떻게 되는가가 궁금합니다.

이전 객체가 GC 되지 않은 상태에서 다시 생성을 하게 되면 이것이 누적이 되어서 오버플로우가 발생하게 되는 게 아닌가 하는 궁금증 입니다.

1개의 좋아요

btnTempStop_Click 하여 Thread가 중지 된 후 다시 btnTempStart_Click을 실행하면 TempThread에는 새로운 객체가 생성되고 TempThread에 있던 이전 객체는 GC 대상이 됩니다.

1개의 좋아요

제가 알기론 Thread는 GC 관리 대상이 아니고 tempThread(쓰레드 핸들러)는 GC로 관리되는 객체
메모리 leak은 없을 듯 합니다.
thread proc(test)내의 지역변수는 쓰레드 종료시 GC로 반환될 것 같고요

1개의 좋아요

flag = false; 는 변수를 바꾸는 거지
Sleep 에 있을 확률이 높죠.

Sleep 끝나고 while 체크 끝나고 종료 될때까지
기다리기 위해서 join 한거고요.

레퍼런스를 물고 있으면 GC가 작동 안되기 때문에
null 할당 하면 날라가죠.

테스트 하면서 의문 든건 Collect 하기 전에 이미
객체 카운트가 바뀌었습니다.

그라목손 님 말처럼 GC 관리 대상이 아닌건지
아님 Debug 모드라 그런건지 확실히는 모르겠네요.

1개의 좋아요

일단 flag처리는 sleep 시간이 길어 질수록 바로 끝내지 못합니다.

cancellationToken.WaitHandle.WaitOne(20); // cancellationToken사용으로 즉시 해제를 하시는게 바람직 해 보입니다.

그리고 TempThread 를 폼의 객체로 선언 하면 폼이 닫힐때 까지 GC가 수집 안하겠죠…
물론 TempThread에서 물리적으로 선언한 쓰래드가 닫혔을떄는 해당 쓰레드는 수집 대상 이겠죠

하지만 차라리 버튼 클릭시 ThreadPool.QueueUserWorkItem 사용 하는게 낫지 않을까요
최대 250개 까지 관리가 되니까요…512개였나…

근디 요샌 TASK 쓰지 않나요 ?

1개의 좋아요

flag = false가 되면 루프 빠지면서 쓰레드 종료 되겠죠
Thread.Sleep(20);는 20ms CPU 점유율 독점 방지 같은데요
flag이 frmMain의 멤버라 스코프를 벗어나야 Collect 되는 게 맞을 것 같습니다.

1개의 좋아요

원본으로 올려주신 글의 서식을 가다듬었습니다.

1개의 좋아요

맴버 필드라고 해서 반드시 GC수집 대상이 아닌 건 아닙니다.

계속 해서 new() 를 통해 인스턴스가 생성 되고 Heap할당 메모리가 부족한 경우

맴버 필드에 사용 되는 인스턴스가 '다른 곳’에서 참조 하는 곳이 없다면

GC에 의해 수집 될 수 있습니다.

만약 스레드는 현재 실행중이라면 루트로 간주하여
해당 폼 클래스의 인스턴스가 사라져도 GC수집의 대상이 될 순 없습니다.
(사실 스레드가 실행중이라면 해당 인스턴스는 사라질 수 도 없습니다…)

2개의 좋아요

일단 닷넷 런타임이 생성할 수 있는 스레드 갯수에는 제한이 없다고 알려져 있습니다.

그런데, 예제의 코드로만 본다면, 생성되는 스레드의 스택 프레임은 매우 작습니다.
산술적으로만 본다면, 그 정도 작은 사이즈로 오버플로우를 발생시킬라면 폐관 수련하는 심정으로 눌러야 되지 않을까요? ^^

사실, 문제는 운영체제에서 발생하지 싶습니다.
아시다시피, 스레드는 근본적으로 CPU의 타임 슬라이스입니다.

스레드 갯수가 과도하게 많아지면, 내 앱뿐만 아니라, 시스템 전체가 전체가 느려지겠죠.
한 마디로 민폐 프로세스가 되지 않을까요?

운영 체제가 이런 민폐 짓에 어떻게 반응하는지, 아래 코드로 테스트 한번 해보시죠.

public partial class frmMain:Form 
{
   // ...
   private const int MAX_THREADS = 1000;

   // ...
   private void btnTempStart_Click(object sender, EventArgs e)
   {
      int count = 0;
      while (count++ < MAX_THREADS)
      {
         var t = new Thread(new ThreadStart(test));
         t.IsBackground = true;
         t.Start();
      }
   // ...
2개의 좋아요

작성해주신 테스트 코드는 질문자님의 원래 질문 테스트에 대해 좀 다른 것 같습니다.

‘생성과 종료’ 반복 했을 경우가 질문인데

저 코드는 당연히 PC마다 다르겠지만 메모리 부족 오류가 발생 될 것입니다.

하지만 바로 스레드를 종료시키면 정상 입니다.

foreach (int n in Enumerable.Range(1, 100000))
{
    _tempThread = new Thread(new ThreadStart(this.Test));
    _tempThread.IsBackground = true;
    _flag = true;
    _tempThread.Start();

    _flag = false;  // 바로 중지
}

의 부분과
중단 답글에 폼의 맴버 필드라서 GC 대상이 아닌듯 하다고 답변 하신 부분도
다음과 같이 테스트 해보면 알 수 있습니다.

private Foo _foo;  // 맴버 필드

foreach (int n in Enumerable.Range(1, 100000))
{
    _foo = new Foo();
}

public class Foo
{
    ~Foo()
    {
        //
    }
}

메모리 판단으로 GC 수집에 의해 종료자 호출이 되는 것을 눈으로 확인해 볼 수 있습니다.

2개의 좋아요

저는 질문자가 중지 버튼을 누르지 않고, 생성 버튼을 계속 누르면 어떻게 되느냐로 이해했습니다.

@aroooong 님의 코드에서 아래의 코드를 뺀 경우를 가정한 것이죠.

_flag = false;  // 바로 중지

질문을 다시 보니…

이러한 상황 자체를 연출하기는 힘들죠. GC 맘이니까요. ^^

2개의 좋아요