멀티 쓰레드 프로그래밍을 할때는 동작중인 모든 쓰레드가 동시에 돌아가고 있기 때문에
같은 데이터를 접근하는 쓰레드들은 철저하게 주의를 기울여야 합니다.
보통 같은 데이터 , 메모리 접근을 차단하기위해 싱크 오브젝트들을 사용하는데는
다음은 CriticalSection 사용중 발생한 문제점의 한 예 입니다.
실제 우리 회사 LogManager 프로젝트의 소스 코드입니다.
아래는 GUI Main 쓰레드에서 CopyData 메시지를 받아서 m_pTemp라는 StringList에서 로그 데이터를 추가하는 루틴입니다.
GUI 쓰레드에서 메시지를 받아 로그 데이터를 추가하고 별도의 태스크 쓰레드에서 List에 있는 로그 데이터를 데이터베이스(Firebird)에 INSERT 하는 구조로 되어있습니다.
//---------------------------------------------------------------------------- // Temp Log 데이터도 삭제 함 : String List에서 max count 초과 Log 삭제 while( m_pTemp->Count > F_OptDlg->E_MaxLogLine->GetInt() ) { EnterCriticalSection(&g_sec); m_pTemp->Delete(0); LeaveCriticalSection(&g_sec); } |
아래는 코드는 DB에 추가하는 소스의 일부이고요 태스크 쓰레드에서 돌고 있습니다.
DB추가 부분 및 관계없는 소스는 생략하였습니다.
m_pTemp에서 로그 데이터를 DB에 추가하고 나중에 Clear() 한다는 점만 보면됩니다.
//Thread에서 Log를 Add하는 경우를 위해 Critical_section으로 묶어 둔다. EnterCriticalSection(&g_sec); for(int i=0;i< m_pTemp->Count;i++) { // DB에 데이터 추가 } m_pTemp->Clear(); LeaveCriticalSection(&g_sec); |
위 두개의 소스에서는 별개 쓰레드가 m_pTemp 라는 TStringList을 접근하기 때문에 안전하게 하기 위해서 EnterCriticalSection(&g_sec); 과 LeaveCriticalSection(&g_sec); 을 사용하여 서로 동시 접근하지 않도록 잘 막아 준것 처럼 보입니다.
하지만 실제로 위 소스로 프로그램을 돌리다면 중간에 예외(EXCEPTION)가 하나 발생합니다.,
out of bounds 에러이지요
에러가 발생하는 시나리오(타이밍)을 보도록 합시다
일단 태스크 쓰레드가 아래 빨간색 부분을 실행 하고 있다고 가정해 보죠
//Thread에서 Log를 Add하는 경우를 위해 Critical_section으로 묶어 둔다. EnterCriticalSection(&g_sec); for(int i=0;i< m_pTemp->Count;i++) { <----------NOW 이부분 실행중 // DB에 데이터 추가 } m_pTemp->Clear(); LeaveCriticalSection(&g_sec); |
이때 Main GUI 쓰레드는 다음 소스의 while 조건문을 체크 하는 타이밍이 나올 수 있습니다.
//---------------------------------------------------------------------------- // Temp Log 데이터도 삭제 함 : String List에서 max count 초과 Log 삭제 while( m_pTemp->Count > F_OptDlg->E_MaxLogLine->GetInt() ) { EnterCriticalSection(&g_sec); m_pTemp->Delete(0); LeaveCriticalSection(&g_sec); } |
<----------NOW 이 부분을 실행중일 때는 태스크쓰레드는 EnterCriticalSection
안으로 들어가 있지만.. Min GUI 쓰레드는 EnterCriticalSection 을 실행하지 않아서 while 조건문까지는 진행됩니다.
m_pTemp->Clear(); 를 클리어 하기 전이므로 m_pTemp->Count는 0이 아닐 가능성이 있습니다.
이 다음 라인에서 기다리다가 위의 태스크 쓰레드가 m_pTemp->Clear(); 를 실행하고 빠져나갈때
GUI 쓰레드는 m_pTemp->Delete(0); 를 실행하게 됩니다.
이때는 물론 m_pTemp->Clear()가 실행되어 m_pTemp에는 아이템이 전혀 없습니다.
따라서 아이템이 없는 StringList를 접그 하려는 m_pTemp->Delete(0); 에서 EXCEPTION 이 발생하게 됩니다.
위코드는 다음과 같이 EnterCriticalSection를 while 조건문 밖으로 빼내어야 멀티쓰레드 동시 접근 문제를 완벽하게 막는 구조가 됩니다.
// Temp Log 데이터도 삭제 함 : String List에서 max count 초과 Log 삭제 EnterCriticalSection(&g_sec); // CriticalSection을 밖으로 뺌 while( m_pTemp->Count > F_OptDlg->E_MaxLogLine->GetInt() ) { m_pTemp->Delete(0); } LeaveCriticalSection(&g_sec); |
위 문제는 다행히 out of bounds 에러가 자주 발생하기 때문에 금방 발견이 되었지만
실제로 멀티쓰레드에서 발생하는 동시 접근으로 인한 버그는 찾아내기가 굉장히 어렵습니다.
위 내용은 비교적 간단한 시나리오로 멀티 쓰레드에서 발생할 수 있는 버그를 이해하기 쉽게 단적으로 잘 보여주는 예인것 같습니다. 실전에서는 항상 이런 문제에 주의를 집중하여 코드를 작성할 필요가 있습니다.
참고바랍니다.
'프로그래밍' 카테고리의 다른 글
TDateTime의 DecodeTime() (1) | 2009.11.02 |
---|---|
컴파일에러:E2111 Type 'typename' may not be defined here (3) | 2009.10.27 |
C소스의 전체 컴파일 과정 (0) | 2009.10.15 |
스레드 종료(TerminateThread API)에 대해서... ] (0) | 2009.10.09 |
객체지향 설계의 원칙 SOLID (1) | 2009.10.09 |