아름답게(?) 작성되지 않은 레거시 프로그램을 유지보수하다 보면 쓰레드 종료를 안전하게 하지 않은 코드 패턴을 종종 발견한다. 이로 인해 생긴 교묘한 버그로 고생한 경험이 있어서 어떻게 쓰레드를 종료하는 것이 안전한 것인지 정리해 두려고 한다.
쓰레드를 안전하게 종료해 주기 위해서는 쓰레드 코드와 쓰레드를 종료해 주는 코드 양쪽 모두 잘 작성되어야 한다.
쓰레드 코드는 아래의 3가지 조건을 항상 만족하도록 작성되어야 한다.
DWORD __stdcall ThreadFunc(LPVOID param) {
// 파일 열기
fpPB = fopen("Temp.dat", "ab");
while (bStop == FALSE) {
...
fwrite(TempData, size, 1, fpPB);
...
}
// 파일 닫기
fclose(fpPB);
}
Thread Terminator 는 Thread 에게 실행 종료를 알리고(플래그 또는 이벤트 객체를 통해), 실제로 Thread가 종료될 때 까지 기다리도록 한다.
void Some()
{
// 쓰레드 생성
handle = CreateThread ...
if (handle == NULL) { ... }
// do something...
// 쓰레드 종료 신호 전달
bStop = TRUE;
// 실제 쓰레드 종료 대기
WaitForSingleObject(handle, INFINITE);
// 쓰레드 핸들 닫기
if (handle != NULL) {
CloseHandle(handle);
handle = NULL;
}
}
흔히 잘못 작성하는 쓰레드 종료 코드를 Worst 부터 정리해 보면 크게 두가지 유형이 있는 것 같다.
Worst 케이스는 TerminateThread()를 통해 강제로 아무 시점에나 쓰레드를 종료하는 방법이다.
MSDN에서 TerminateThread() 함수 설명을 보면 엄청난 경고를 하고 있다.
TerminateThread is used to cause a thread to exit. When this occurs, the target thread has no chance to execute any user-mode code. DLLs attached to the thread are not notified that the thread is terminating. The system frees the thread's initial stack.
...
TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:
두번 째 좋지 않은 방법은 Thread 종료를 위해 단지 종료하라는 신호만 주고 쓰레드 종료를 실제 기다리지 않는 코드이다. 이와 같은 경우 여러 환경에 따라 미묘한 버그를 만들어 개발자를 많이 힘들게 하는 경우가 있는 것 같다.
쓰레드는 반드시 예외없이 안전하게 종료되어야 한다. 만약 이 규칙을 지키지 않는다면 매우 간헐적으로 발생하는 수 많은 버그들에 고통 받게 될 가능성이 크다.