티스토리 뷰

상황 :
 간단한 이벤트 큐를 만들어야 하는 상황이 발생했다.
 각종 read/write 작업들을 싱글톤 이벤트 큐에 집어 넣고, 몇개의 Thread들이 큐를 감시하다, 큐에 새로운 이벤트가 들어 오면 이벤트에 따라 적적한 작업을 해주는 방식이었다.
 

시간

ThreadA

ThreadB

1

if(queue.empty()) -> not empty

 

2

 

if(queue.empty())-> not empty too

3

queue.pop()

 

4

 

queue.pop() -> 큐는 이미 비었다 Error!!


 위와 같은 문제 때문에 Thread 가 큐에서 pop을 하는 작업은 동기화를 걸어 주어야 했다.
 
 구조는 아래와 같았다.
 getInstance() 함수로 EventQueue의 생성자가 가 호출 되면 생성자 내부에서 WorkerThread들을 호출 하고, WorkerThread들은 EventQueue를 루프를 돌면서 확인한다(지면상 간단히 상상이 가능한 부분은 생략 하도록 한다. 별로 중요한 부분은 아니니 대충 보고 넘어가도 된다.)

queue.h

class EventQueue {
    public :
        virtual ~EventQueue();
        /**
          Singleton 객체 생성
        */
        static EventQueue* getInstance() {
            if(m_selfInstance == NULL) {
                m_selfInstance = new EventQueue();
            }
            return m_selfInstance;
        }

        bool push(EVENT event); // 구현 생략
        EVENT pop();                 // 구현 생략
        bool empty();                  // 구현 생략, 비었으면 true, 그렇지 않으면 false

    private :
        EventQueue() {
            pthread_mutex_init(&m_mutex, NULL);
            for(int i=0; i<WORKERTHREAD_COUNT; i++) {
                m_thread[i].init(&m_mutex);  // thread에게 뮤텍스의 포인터를 넘겨 준다.
                m_thread[i].start();              // create_thread 류의 함수를 호출하여 thread를 동작 시킨다.
            }
        }

        typedef std::queue<EVENT> EVENT_QUEUE;

        static EventQueue*  m_selfInstance;
        EventWorkerThread   m_thread[WORKERTHREAD_COUNT];
        pthread_mutex_t     m_mutex;
};

thread.cpp
void EventWorkerThread::init(pthread_mutex_t* mutex) {
    m_mutex = mutex; //threa의 멤버 변수에 EventQueue의 mutex 변수 포인터를 넘겨 준다.
                             // 이로써 mutux를 공유 할 수 있다.
}

/**
  실제 thread가 작업을 하는 부분이다.
  create_thread 라던지 그런것은 다 접어 두고, 그냥 thread가 이런 작업을 하게끔 되어 있구나 라고 생각하자.
*/
void* EventWorkerThread::run() {
    while(1) {
        /* critical section.. 동기화가 제대로 된다면 1 : ...., 2 : .... 의 형식으로 나와야 한다.*/
        pthread_mutex_lock(m_mutex);  // EventQueue로 부터 넘겨 받아온 mutex 포인터다
        std::cout << "1 : " << (int)this << ":" << (int)m_mutex << std::endl;
        if(!EventQueue::getInstance()->empty()) {
            m_event = EventQueue::getInstance()->pop();
        }
        std::cout << "2 : " << (int)this << ":" << (int)m_mutex << std::endl;
        pthread_mutex_unlock(m_mutex);

        if(m_event.type & EVENT_WRITE) {
              // 이벤트가 발생 하면 뭔가를 하긴 한다.
        }
    }
}

문제 :
  동기화가 전혀 안되고 있다.

원인 :
  Singleton과 그 singleton 객체를 참조하는 Thread 군이 문제!!
  시나리오를 보도록 하자(지금 부터 중요!!).
 
1.  어딘가에서  EventQueue에 뭔가를 넣기 위해 singleton 객체를 호출 한다고 하자.
  static EventQueue* getInstance() {
        if(m_selfInstance == NULL) {
            m_selfInstance = new EventQueue();   //현재 프로세스가 여기 까지 왔다.
                                                                   // if(m_selfInstance == NULL) 를 통과 했으므로
                                                                   // 현재 객체가 없는 상태라고 판단 했다는 것을 잊지 말아야 한다.
                                                                   //그러면 이제 생성자를 보도록 하자.
        }
        return m_selfInstance;
  }

2. 쓰레드 생성
EventQueue() {
    pthread_mutex_init(&m_mutex, NULL);
    for(int i=0; i<WORKERTHREAD_COUNT; i++) {
        m_thread[i].init(&m_mutex);  // thread에게 뮤텍스의 포인터를 넘겨 준다.
        m_thread[i].start();              // 이때 thread가 생성 된다!!
                                                 // 생성 되고 난후 바로 EventQueu를 참조하는 것이 문제!!
    }
}

3. WokerThread가 EventQueue 감시
pthread_mutex_lock(m_mutex);  // EventQueue로 부터 넘겨 받아온 mutex 포인터다
std::cout << "1 : " << (int)this << ":" << (int)m_mutex << std::endl;
if(!EventQueue::getInstance()->empty()) {
    m_event = EventQueue::getInstance()->pop();
}
std::cout << "2 : " << (int)this << ":" << (int)m_mutex << std::endl;
pthread_mutex_unlock(m_mutex);

 굵은 글씨로 되어 있는 부분에 주목하자!! 자 다시 getInstance()를 호출하고 있다. 아까 1번 과정에서 아직 EventQueue 객체가 할당 되지 않은 상태라고 판단하고 있다는 사실을 잊지 말자!! 여기서 EventQueue의 또 다른 객체가 생성되고 또 다른 mutex 객체가 생성된다.

해결 :
  생성자에 lock을 걸어 주던지, getInstance가 호출 완료 되고 난뒤에 thread를 초기화 하는 모듈을 호출하거나.

오늘의 교훈 :
  singleton을 남발 하지 말자.
  thread safe하게 코드를 읽는 버릇을 들이자.
  thread는 무섭다.

참고 :
  오늘도 우리의 pudae 님께서 도와 주셨다..부끄러울 따름이다.

email :
 kukuta@gmail.com

'진리는어디에' 카테고리의 다른 글

extern  (0) 2007.02.23
소켓 강제 종료시 파이프(pipe) 깨짐  (0) 2007.01.27
Singleton vs Critical section  (0) 2007.01.10
TIME_WAIT state vs SO_REUSEADDR option  (4) 2007.01.08
Edge trigger vs Level trigger  (2) 2007.01.05
const vs mutable  (4) 2006.12.21
TAG
,
댓글
댓글쓰기 폼