Event클래스입니다.

s911310의 이미지

금융쪽 온라인 서버 프로그램을 만들고 있는데 쓰레드 안에서 쓸 Event클래스를 만들어 봤습니다. 버그나 기타 등등, 고수님들의 의견을 듣고 싶습니다.

//
// 클래스명 : 이벤트 동기화 클래스
// 개    요 : 이벤트 동기화 클래스
// 주의사항 : 없음
//
class CEvent {
    
protected:
    
    #if defined(WIN32)
    CRITICAL_SECTION    m_hSyncObj;    // 동기화 객체 핸들
    HANDLE              m_hEvent;    // 이벤트 핸들
    #else
    pthread_mutex_t     m_hSyncObj;    // 뮤텍스 ID
    pthread_cond_t      m_hEvent;    // 이벤트 핸들
    #endif
    BOOL                m_bSignal;    // 시그널 발생 플래그
    
private:    
    
    //
    // 조 작 명 : 엑세스 권한 취득
    // 개    요 : 동기화 객체에 대한 엑세스 권한을 취득한다.
    // 함 수 형 : VOID
    // 함 수 명 : Lock
    // 파라미터 : 없음
    // 리 턴 값 : 없음
    // 주의사항 : 없음 
    //
    VOID Lock()
    {
        #if defined(WIN32)
        ::EnterCriticalSection(&m_hSyncObj);
        #else
        ::pthread_mutex_lock(&m_hSyncObj);
        #endif
    };
    
    //
    // 조 작 명 : 엑세스 권한 해제
    // 개    요 : 취득한 엑세스 권한을 해제한다.
    // 함 수 형 : VOID
    // 함 수 명 : Unlock
    // 파라미터 : 없음
    // 리 턴 값 : 없음
    // 주의사항 : 없음 
    //
    VOID Unlock()
    {
        #if defined(WIN32)
        ::LeaveCriticalSection(&m_hSyncObj);
        #else    
        ::pthread_mutex_unlock(&m_hSyncObj);
        #endif
    };
    
public:
    
    //
    // 조 작 명 : 이벤트 동기화 클래스 생성자
    // 파라미터 : 없음
    // 주의사항 : 없음 
    //
    CEvent() : m_bSignal(FALSE)
    {
        #if defined(WIN32)
        ::InitializeCriticalSection(&m_hSyncObj);
        
        if ((m_hEvent = ::CreateEvent(NULL, TRUE, m_bSignal, NULL)) == NULL) {
            abort();
        }
        #else
        if ((::pthread_mutex_init(&m_hSyncObj, NULL) != 0) ||
                (::pthread_cond_init(&m_hEvent, NULL) != 0)) {
            abort();
        }
        #endif
    };
    
    //
    // 조 작 명 : 이벤트 동기화 클래스 소멸자
    // 파라미터 : 없음
    // 주의사항 : 없음 
    //
    ~CEvent()
    {
        #if defined(WIN32)
        ::DeleteCriticalSection(&m_hSyncObj);
        ::CloseHandle(m_hEvent);
        #else
        ::pthread_mutex_destroy(&m_hSyncObj);
        ::pthread_cond_destroy(&m_hEvent);
        #endif
    };
    
    //
    // 조 작 명 : 이벤트 대기
    // 개    요 : 이벤트가 발생할 때 까지 대기한다.
    // 함 수 형 : VOID
    // 함 수 명 : WaitEvent
    // 파라미터 : 없음 
    // 리 턴 값 : 없음
    // 주의사항 : 없음 
    //
    VOID WaitEvent()
    {
        #if defined(WIN32)
        while (m_bSignal != TRUE) {
            if (::WaitForSingleObject(m_hEvent, 1000) == WAIT_OBJECT_0) {
                break;
            }
        }
        #else
        while (m_bSignal != TRUE) {
            struct timespec    sTimeSpec;
            int                nReturn;
            
            sTimeSpec.tv_sec  = time(NULL) + 1;
            sTimeSpec.tv_nsec = 0;
            
            ::pthread_mutex_lock(&m_hSyncObj);
            nReturn = ::pthread_cond_timedwait(&m_hEvent, &m_hSyncObj, &sTimeSpec);
            ::pthread_mutex_unlock(&m_hSyncObj);
            
            if (nReturn == 0) {
                break;
            }
        }
        #endif
    };
    
    //
    // 조 작 명 : 이벤트 발생
    // 개    요 : 이벤트를 발생시킨다.
    // 함 수 형 : VOID
    // 함 수 명 : SetEvent
    // 파라미터 : 없음
    // 리 턴 값 : 없음
    // 주의사항 : 없음 
    //
    VOID SetEvent()
    {
        Lock();
    
        if (m_bSignal != TRUE) {
            m_bSignal = TRUE;
            #if defined(WIN32)
            ::SetEvent(m_hEvent);
            #else
            ::pthread_cond_broadcast(&m_hEvent);
            #endif
        }
    
        Unlock();
    };
    
    //
    // 조 작 명 : 이벤트 리셋
    // 개    요 : 이벤트를 리셋시킨다.
    // 함 수 형 : BOOL
    // 함 수 명 : ResetEvent
    // 파라미터 : 없음
    // 리 턴 값 : 없음
    // 주의사항 : 없음 
    //
    VOID ResetEvent()
    {
        Lock();
    
        if (m_bSignal == TRUE) {
            m_bSignal = FALSE;
            #if defined(WIN32)
            ::ResetEvent(m_hEvent);
            #else
            ::pthread_cond_broadcast(&m_hEvent);
            #endif
        }
    
        Unlock();
    };
};

현재 제가 작성하고 있는 서버 프로그램은 윈도우즈 2000 및 솔라리스 환경에서 동일하게 움직하여 하는 사양이라서 OS나 컴파일 환경에 따라 서버 본체 부분이 Event객체를 호출시 그러한 환경을 인식하지 않기를 원하고 있습니다.
m_bSignal 변수는 WaitEvent() 호출시 이벤트가 설정되어 있으면WaitForSingleObject 나 pthread_cond_timedwait를 호출할 필요없이 바로 리턴되기 위해 사용하고 있고 현재 WaitEvent() 사양은 타임 아웃이 필요없이 이벤트가 발생하지 않는한 계속 블럭되어 있는 사양입니다.

김성진의 이미지

원래 프로그래머는 자신의 코드에 대해 왈가왈부하는 것을
좋아하지 않는데, 이렇게 코드를 올리신 것을 보니
존경스럽습니다.

이 코드를 보고, 두가지를 말씀드리고자 합니다.

첫번째는 이 객체가 얼마나 큰 응용프로그램에 적용될 지 모르겠지만,
에러처리가 미흡합니다.
lock, unlock같은 함수는 대부분 에러가 발생하지 않는 함수라고 생각되지만,
memory corruption이나, uninitialized mutex의 경우에는
에러가 발생하기 때문에 습관적으로 리턴형이 void가 아닌
라이브러리 함수는 리턴값을 체크하는 것이 좋을 것 같습니다.
아니, 반드시 하셔야 합니다.
만일 리턴값이 존재함에도 불구하고, 굳이 무시를 하셔야 한다면,
함수 호출 앞부분에 (void)라고, 타입캐스팅을 해 주시는게
유지보수 입장에서도 의미가 명확하니까 좋을 것 같습니다.

둘째, pthread_cond_ 계열 함수에 관해서입니다.
SMP환경의 solaris의 경우에는 이 함수에서 깨어났을 때,
누군가가 pthread_signal(), pthread_broadcast()를 불려준
상태라고, 가정해서는 안된다고 나와있습니다.

즉, 가짜로( spurious wakeup) 깨어나는 현상이 존재하기 때문에
cond_wait()이후에 깨어났을 때 별도의 플래그를 두어서,
정말로 시그날에 의해 깨어난 것인지, 더블 체크를 하시는게
동작을 완벽성을 보장하는데 도움이 될 것 같습니다.

다른 이야기지만 한가지 더 사족을 달면,
지금 만드시 클래스가 어떤 Layer에 존재하는 지 잘 모르겠지만,
의미코드의 구현레벨과 라이브러리 레벨을 분리하는 것이
나중에 유지보수 및 포팅시에 도움이 될 것 같습니다.
지금 구현하신 이벤트 객체는 의도한 이벤트 로직 외에도
플랫폼 의존성을 제거하기 위한 로직까지 함께 포함되어 있습니다.

즉, 하위에 포팅 계층을 하나 더 두어서 mutex, cond_wait() 관련한
라이브러리 함수를 한번더 추상화하는 것이 더 좋을 것이라는
생각도 해 봅니다.

좋은 하루 되세요.

김성진 드림

고도의 추상화, 극도의 구체화, 에디슨을 그리워하다.

bugiii의 이미지

이벤트 객체 동작에 굳이 Critical Section 을 감싼 이유가 궁금하군요. (win32만의 경우라면 저는 절대 필요없다고 생각합니다.) 필요하다고 해도, 코드와 같은 Lock / Unlock 은 예외상황에 대처할 수 없습니다. 그리고 m_bSignal 이라는 변수가 왜 사용되어야 하는지 모르겠습니다.

생성자의 abort() .... 예외로 빼는 것이 좋다고 생각합니다. abort는 라이브러리 사용자에게 어떠한 선택권도 줄 수 없습니다. 최소한 예외라도 발생시키면 바깥에서 어떻게든 처리할 수 있지 않겠습니까.

Wait 함수의 문제점도 있는데, 얼마를 기다릴 것인가에 대한 파라미터가 있어야 하지 않을까요? 무조건 1초만 기다리도록 되어 있고 만약 m_bSignal 이 조건이 맞지 않다면 다시 반복하게 됩니다. 만약 영원히 기다릴 것이라면 굳이 1초라는 시간을 줄 필요가 없고 while 문도 필요가 없을 것입니다. 그리고, Wait 함수의 리턴값이 정상적인 이벤트 발생인지, 타임아웃인지를 구분할 수 있어야 하겠습니다. 그리고, WaitForSingleObject 의 리턴값 검사도 빠져있구요. Wait 를 빠져나오는 다른 조건 (Wait*Ex 류 를 쓰든가 다른 커널 객체를 쓰든가 해서) 이 Wait 에는 필요할 수도 있습니다. 예를 들자면 좀 우아하게 전 쓰레드의 정상 종료를 요구하는 상황에는 WaitForSingleObject로는 어렵습니다.

또하나는... Win32 이벤트 객체는 두가지 동작 방식이 있는데 이것은 그중 하나만을 처리하고 있습니다. 또한 이름을 가지는 이벤트의 경우도 있고 이것을 오픈해야 하는 경우도 반드시 필요합니다. 김성진님도 말씀하셨지만 좀 더 명확히 구현을 한 클래스를 가지고 랩퍼를 씌우는게 낫지 않을까 합니다.

GNU의 common c++ 인가 하는 라이브러리를 한번 보시면 좋겠습니다. (물론 거기도 win32의 세세한 기능을 다 사용하는 것은 아닙니다만...)

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.