2009. 5. 21. 18:49

ABA Problem

위키디피아에서의 ABA Problem 설명은 lock free stack구현에서의 문제점을 예제로 들고 있습니다.

 

/* Naive lock-free stack which suffers from ABA problem.*

class Stack {


   volatile Obj* top_ptr;

   // Pops the top object and returns a pointer to it.
   Obj* Pop() {
     while(1) {
       Obj* ret_ptr = top_ptr;

       if (!ret_ptr) return NULL;       
       // ** 실행 구간 1
       Obj* next_ptr = ret_ptr->next;          
       // ** 실행 구간 2
       if (CompareAndSwap(top_ptr, ret_ptr, next_ptr)) { 
         return ret_ptr;
       }
       // The stack has changed, start over.
     }
   }
}; 
언뜻 봐서는 코드상으로는 크게 문제가 없어 보이며, 실제로도 자체 구현상으로는 거의 완벽한 
lock free stack이 구현된것으로 생각됩니다.
(** CompareAndSwap()은 인자 1,2가 같은값일때, 1번인자의 값을 3번 값으로 swap시킵니다. 
 Atomic Operations 이므로 매우 빠릅니다.)
그러나, 아래의 시나리오를 봅시다.
스택이 top->A->B->C 로 초기화되어 있다고 가정했을때,


// --- 1번 Thread 작업 --

// 1번 Thread가 pop을 실행합니다.

 ret = A;
 next = B;

// --- 1번 Thread 작업 중 작업전환 -- 

// --- 2번 Thread 작업 -- 

// 2번 스레드의 pop이 실행됩니다.

 { 
   ret = A;   // 1번 스레드의 pop이 끝까지 실행되지 못했으므로, top은 A입니다
   next = B;
   CompareAndSwap(A, A, B)  // 성공, top은 B가 됩니다.
   return A;
 } // 스택상태는 top -> B -> C 이 됩니다.
 { // 2번 쓰레드가 한번더 pop을 실행합니다.
   ret = B;
   next = C;
   CompareAndSwap(B, B, C)  // 성공, top은 C가 되었습니다.
   return B;
 } // 스택상태는 top -> C 이 됩니다. 
 delete B;  // 2번 쓰레드에서 B값을 삭제 합니다.
 { // 2번 쓰레드에서 아까 위해서 pop된 A를 push합니다.
   A->next = C;
   CompareAndSwap(C, C, A)  // 성공,  top은 A가 됩니다.
 } // 스택상태는 top -> A -> C 이 됩니다. 

// --- 2번 Thread 작업 중 작업전환-- 


// --- 1번 Thread 작업 다시 실행-- CompareExchange(A, A, B); 
 // top과 return값이 모두 A이므로 성공이 됩니다.
 // 그러나 B는 2번 Thread에서 삭제가 되어 있으므로, 이 구간 부터, stack은 잘못된 메모리
 // 를 참조하게 됩니다.
 // 이 상황을 ABA Problem 이라고 합니다.
 
쉽게 일어나지 않을 상황 같지만, 일어나지 않는다는 보장도 없는 상황입니다.
물론 C#이나, Java같이 가비지컬렉션을 사용하는 언어에서는 발생되지 않을 문제이지만, 
C계열에서는 반드시 고려가 되어야 될 문제이기도 합니다. 
당장 생각나는 해법으로는 
1. 그냥 sync를 시키면서, 표준 stack을 사용
2. smart pointer를 사용
 
정도가 될거 같지만, 좀더 고민이 필요한 문제일거 같습니다
 
역시 병렬프로그래밍의 길은 멀고도 험한거 같네요 :)