#ifndef __ZCPPMAIN__PROCESS_H__ #define __ZCPPMAIN__PROCESS_H__ #include #include "ZCppMain/ZMainHead.H" #ifdef _DEBUG_CEXCEPT_EXIT_ #include #endif namespace ZNsMain { namespace ZNsIFace { class ZISyncEasy { public: bool Lock (){return false;} bool UnLock(){return false;} public: };/* class ZISyncEasy*/ }/* namespace ZNsIFace*/ template class ZtCExceptSync : public ZNsMain::ZCExceptBase { private: int mi_ErrNum ; int mi_LineNum ; TString mo_FileName; public : ZtCExceptSync(int AI_LineNum=0): mi_LineNum(AI_LineNum){mi_ErrNum=0;} ZtCExceptSync(const char* AP_FileName, int AI_LineNum=0, int AI_ErrNum=0): mi_LineNum(AI_LineNum) { mi_ErrNum =AI_ErrNum ; mo_FileName=AP_FileName; #ifdef _DEBUG_CEXCEPT_EXIT_ std::cerr<<"★★ FileName="< class ZtCSyncCount : public TSemaphore /////////////////// { protected: int mi_LockCount; public : ZtCSyncCount(int AI_LockCount=0) : TSemaphore() { mi_LockCount=AI_LockCount; }/* ZtCSyncCount(int AI_LockCount=0)*/ bool LockCount() { --mi_LockCount; return this->TSemaphore::Lock(); }/* bool LockCount()*/ template bool LockCount(TSyncExec& AR_CSyncExec) { --mi_LockCount; AR_CSyncExec.UnLock(); { if(this->TSemaphore::Lock()==false) return false; } AR_CSyncExec.Lock(); return false; }/* template bool LockCount(TSyncExec& AR_CSyncExec) */ // cf) linux 의 semctl(), semop() 계열의 세마포어에서는 LockTime() 함수가 없다. template int LockCountTime(TSyncExec& AR_CSyncExec, int AI_TimeOutMili) { int VI_LockCode=ZNsMain::ZNsEnum::ZEThread_OK; --mi_LockCount; AR_CSyncExec.UnLock(); { VI_LockCode=this->TSemaphore::LockTime(AI_TimeOutMili); } AR_CSyncExec.Lock(); if(VI_LockCode==ZNsMain::ZNsEnum::ZEThread_TimeOut) { ++mi_LockCount; }/* if(VI_LockCode==ZNsMain::ZNsEnum::ZEThread_TimeOut)*/ return VI_LockCode; }/* template int LockCountTime(TSyncExec& AR_CSyncExec, int AI_TimeOutMili) */ bool UnLockCount() { ++mi_LockCount; return this->TSemaphore::UnLock(); }/* bool UnLockCount()*/ int GetLockCount() const { return mi_LockCount; }/* int GetLockCount() const*/ public: };/* template< typename TSemaphore=ZNsMain::ZCThreadSemaphore > class ZtCSyncCount : public TSemaphore /////////////////*/ /*//////////////////////////////////////////////////////////// ■ class ZtCSyncCountRef 는 ZtCSyncCount 와 비슷하지만, 대기 동기화 object 를 생성자에서 참조로 받는다는 것이 다르다. ////////////////////////////////////////////////////////////*/ template< typename TSemaphore=ZNsMain::ZCThreadSemaphore > class ZtCSyncCountRef //////////////////////////////////// { private : ZtCSyncCountRef(const ZtCSyncCountRef& rhs) { }/* ZtCSyncCountRef(const ZtCSyncCountRef& rhs)*/ ZtCSyncCountRef& operator=(const ZtCSyncCountRef& rhs) { return *this; }/* ZtCSyncCountRef& operator=(const ZtCSyncCountRef& rhs)*/ protected: TSemaphore& mr_CSemaphore; int mi_LockCount ; public : ZtCSyncCountRef(TSemaphore& AR_CSemaphore, int AI_LockCount) : mr_CSemaphore(AR_CSemaphore) { mi_LockCount=AI_LockCount; }/* ZtCSyncCountRef(TSemaphore& AR_CSemaphore, int AI_LockCount)*/ bool LockCount() { --mi_LockCount; return mr_CSemaphore.Lock(); }/* bool LockCount()*/ template bool LockCount(TSyncExec& AR_CSyncExec) { --mi_LockCount; AR_CSyncExec.UnLock(); { if(mr_CSemaphore.Lock()==false) return false; } AR_CSyncExec.Lock(); return true; }/* template bool LockCount(TSyncExec& AR_CSyncExec) */ template int LockCountTime(TSyncExec& AR_CSyncExec, int AI_TimeOutMili) { int VI_LockCode=ZNsMain::ZNsEnum::ZEThread_OK; --mi_LockCount; AR_CSyncExec.UnLock(); { VI_LockCode=mr_CSemaphore.LockTime(AI_TimeOutMili); } AR_CSyncExec.Lock(); if(VI_LockCode==ZNsMain::ZNsEnum::ZEThread_TimeOut) { ++mi_LockCount; }/* if(VI_LockCode==ZNsMain::ZNsEnum::ZEThread_TimeOut)*/ return VI_LockCode; }/* template int LockCountTime(TSyncExec& AR_CSyncExec, int AI_TimeOutMili) */ bool UnLockCount() { ++mi_LockCount; return mr_CSemaphore.UnLock(); }/* bool UnLockCount()*/ int GetLockCount() const { return mi_LockCount; }/* int GetLockCount() const*/ public: };/* template< typename TSemaphore=ZNsMain::ZCThreadSemaphore > class ZtCSyncCountRef //////////////////////////////////*/ /*/////////////////////////////////////////////////////////////////////////////////// ■ ZtCThreadEx<> 클래스 템플릿은 ZNsMain::ZtCThread<> 와는 달리 반드시 상속해서 사용해야 하며, 상속함수에서 Exec() 함수를 재정의하면 된다. 필요하면 Init(), Fini() 도 재정의한다. ■ void Exec(THelpKey AR_HelpKey) 멤버를 사용하고 싶다면 Init(AR_HelpKey), Fini(AR_HelpKey), Exec(AR_HelpKey) 함수는 상속 클래스에서 public 함수로 선언하는 것이 좋겠다. 이게 싫다면 상속클래스 ZtCThreadEx<>::ZtCHelpKeyParam<> 템플릿에 대하여 friend 선언을 해주어야 하는데 약 간 귀찮은 일이다. cf) template template friend class A::B; ■ 2 개의 템플릿 인자를 갖는 멤버 Exec2(THelpKey1, THelpKey2) 를 Exec 멤버를 중첩시켜 Exec(THelpKey1,THelpKey2) 처럼 하면 멤버 은폐로 인해 골치아픈 일이 발생하게 된다. ZtCThreadEx<> 클래스 템플릿은 반드시 상속해서 사용하고 있는데, 상속 클래스에서 Exec(THelpKey1) 을 재정의하면, 멤버 '은폐'로 인해 기존에 상속클래스에서 Init(THelpKey1), Fini(THelpKey1) 를 재정의하지 않은 경우에, 모두 재정의해야한다. 상속 클래스에서 재정의하지 않으면 기본적으로 ZtCThreadEx<>::Init(THelpKey1) ZtCThreadEx<>::Fini(THelpKey1) 이것이 호출되리라는 편견(?)은 버려야 한다. -- 2010-02-21 20:05:00 Exec2(THelpKey1,THelpKey2) 을 사용하는 경우, 상속 클래스에서 Init2(THelpKey1,THelpKey2) 와 Fini2(THelpKey1,THelpKey2) 를 같이 정의해주어야 한다. CWorkPool.H 의 CTypeCtrlSnglAllocWork_T<>::CSnglAllocWork::Init2(~) CTypeCtrlSnglAllocWork_T<>::CSnglAllocWork::Fini2(~) 정의에서 this->CThreadBase::template Init (AR_HelpKey1, AR_HelpKey2) this->CThreadBase::template Fini (AR_HelpKey1, AR_HelpKey2) 가 아니라, this->CThreadBase::template Init2(AR_HelpKey1, AR_HelpKey2) this->CThreadBase::template Fini2(AR_HelpKey1, AR_HelpKey2) 이 맞다. 이것 때문에 2~3 시간 헤맸다. -- 2011-02-03 14:34:00 ///////////////////////////////////////////////////////////////////////////////////*/ template< typename TDerive, typename TThread=ZtCThread<> > class ZtCThreadEx : public TThread /////////////////////// { public : typedef ZtCThreadEx ZCThreadEx; typedef TDerive ZCDerive ; protected: template class ZtCHelpKeyParam { private: friend class ZtCThreadEx; private: THelpKey mr_HelpKey; ZCDerive* mp_CDerive; private: ZtCHelpKeyParam(THelpKey AR_HelpKey, ZCDerive& AR_CDerive) : mr_HelpKey(AR_HelpKey) { mp_CDerive = &AR_CDerive; }/* ZtCHelpKeyParam(THelpKey AR_HelpKey, ZCDerive& AR_CDerive)*/ /*//////////////////////////////////////////////////////////////////////// ■ (*mp_CDerive) 의 멤버 Init,Exec,Fini 는 반드시 템플릿 멤버이어야 한다. "->template" 형식의 템플릿 멤버 표기로 접근하고 있기 때문이다. -- 2009-08-01 20:05:00 ////////////////////////////////////////////////////////////////////////*/ #if(_CODE_OLD_) void Init(){mp_CDerive->template Init(mr_HelpKey);} void Exec(){mp_CDerive->template Exec(mr_HelpKey);} void Fini(){mp_CDerive->template Fini(mr_HelpKey);} #else void Init(){mp_CDerive->Init(mr_HelpKey);} void Exec(){mp_CDerive->Exec(mr_HelpKey);} void Fini(){mp_CDerive->Fini(mr_HelpKey);} #endif private: };/* template class ZtCHelpKeyParam */ template class ZtCHelpKeyParam2 { private: friend class ZtCThreadEx; private: THelpKey1 mr_HelpKey1; THelpKey2 mr_HelpKey2; ZCDerive* mp_CDerive ; private: ZtCHelpKeyParam2(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, ZCDerive& AR_CDerive) : mr_HelpKey1(AR_HelpKey1), mr_HelpKey2(AR_HelpKey2) { mp_CDerive=&AR_CDerive; }/* ZtCHelpKeyParam2(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, ZCDerive& AR_CDerive)*/ /*//////////////////////////////////////////////////////////////////////// ■ (*mp_CDerive) 의 멤버 Init, Exec, Fini 는 반드시 템플릿 멤버이어야 한다. "->template" 형식의 템플릿 멤버 표기로 접근하고 있기 때문이다. -- 2009-08-01 20:05:00 ////////////////////////////////////////////////////////////////////////*/ #if(_CODE_OLD_) void Init2(){mp_CDerive->template Init2(mr_HelpKey1, mr_HelpKey2);} void Exec2(){mp_CDerive->template Exec2(mr_HelpKey1, mr_HelpKey2);} void Fini2(){mp_CDerive->template Fini2(mr_HelpKey1, mr_HelpKey2);} #else void Init(){mp_CDerive->Init(mr_HelpKey1, mr_HelpKey2);} void Exec(){mp_CDerive->Exec(mr_HelpKey1, mr_HelpKey2);} void Fini(){mp_CDerive->Fini(mr_HelpKey1, mr_HelpKey2);} #endif private: };/* template class ZtCHelpKeyParam2 */ template< typename THelpKey1, typename THelpKey2, typename THelpKey3 > class ZtCHelpKeyParam3 /////////////////////////////////////////////// { private: friend class ZtCThreadEx; private: THelpKey1 mr_HelpKey1; THelpKey2 mr_HelpKey2; THelpKey3 mr_HelpKey3; ZCDerive* mp_CDerive ; private: ZtCHelpKeyParam3 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::*/ ( THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, THelpKey3 AR_HelpKey3, ZCDerive& AR_CDerive ) : mr_HelpKey1(AR_HelpKey1), mr_HelpKey2(AR_HelpKey2), mr_HelpKey3(AR_HelpKey3) { mp_CDerive=&AR_CDerive; }/* ZtCHelpKeyParam3(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, THelpKey3 AR_HelpKey3, ZCDerive& AR_CDerive)*/ /*//////////////////////////////////////////////////////////////////////// ■ (*mp_CDerive) 의 멤버 Init, Exec, Fini 는 반드시 템플릿 멤버이어야 한다. "->template" 형식의 템플릿 멤버 표기로 접근하고 있기 때문이다. -- 2009-08-01 20:05:00 ////////////////////////////////////////////////////////////////////////*/ #if(_CODE_OLD_) void Init3(){mp_CDerive->template Init3(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} void Exec3(){mp_CDerive->template Exec3(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} void Fini3(){mp_CDerive->template Fini3(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} #else void Init(){mp_CDerive->Init(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} void Exec(){mp_CDerive->Exec(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} void Fini(){mp_CDerive->Fini(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);} #endif public: };/* template< typename THelpKey1, typename THelpKey2, typename THelpKey3 > class ZtCHelpKeyParam3 /////////////////////////////////////////////*/ /*protected:*/ protected: _VT_ void Init() { this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다. #ifdef _DEBUG std::cout<<"ZCThreadEx::Init()"< _VT_ void Init(THelpKey) { this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다. #ifdef _DEBUG std::cout<<"# ZCThreadEx::Init(THelpKey), THelpKey typename="< _VT_ void Init(THelpKey)*/ template _VT_ void Init(THelpKey1, THelpKey2) { this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다. #ifdef _DEBUG std::cout<<"## ZCThreadEx::Init(THelpKey1, THelpKey2), " "THelpKey1 typename="< _VT_ void Exec(THelpKey) { #ifdef _DEBUG std::cout<<"# ZCThreadEx::Exec(THelpKey), THelpKey typename="< _VT_ void Exec(THelpKey)*/ template _VT_ void Exec(THelpKey1, THelpKey2) { #ifdef _DEBUG std::cout<<"## ZCThreadEx::Exec(THelpKey1, THelpKey2), " "THelpKey1 typename="< _VT_ void Fini(THelpKey) { #ifdef _DEBUG std::cout<<"# ZCThreadEx::Fini(THelpKey), THelpKey typename="< _VT_ void Fini(THelpKey) */ template _VT_ void Fini(THelpKey1, THelpKey2) { #ifdef _DEBUG std::cout<<"## ZCThreadEx::Fini(THelpKey1, THelpKey2), " "THelpKey1 typename="< int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); void CPU_CLR (int cpu, cpu_set_t *set); int CPU_ISSET(int cpu, cpu_set_t *set); void CPU_SET (int cpu, cpu_set_t *set); void CPU_ZERO ( cpu_set_t *set); ■ 조건변수로 쓰레드를 대기시키거나 깨우는 동작을 구현할 때, 일반적으로 쓰레드가 잠자 고 있는지 동작중인지를 판단하는 변수와, 쓰레드를 재워야하는지 아닌지를 표시하는 변 수, 이렇게 2 개가 필요하다. 그렇지 않다. ZtCMutexCondVar 을 보면, bool 멤버 하나로도 괜찮다. -- 2025-10-04 19:21 ■ 긴 작업을 하고 있는 쓰레드의 경우, 그 쓰레드가 현재 어디까지 작업을 하고 있는지 알 고 싶을 때가 있다. 이때 작업 진행 정보를 멤버 변수에 담아 두게 되는데, 다른 쓰레드 에서 이 진행 정보 멤버에 접근할 때, 해당 멤버 변수가 int 나 long 과 같은 primitive 인 경우는 상관이 없으나 문자열 같은 동적버퍼를 가지고 있는 object 인 경우, 동적 버 퍼의 동시 접근 과정에서 오류가 발생할 수 있다. 이런 때는 '진행 정보'만을 별도로 담당하는 내포 클래스를 만들어서 해결하자. 이 '진행 정보' 용 내포클래스는 동기화 object 를 가지고 있고, 이 object 에 접근하는 코드는 동 기화 object 멤버를 통해서, 동기화 영역으로 묶어주어야 한다. 진행 정보가 다소 많다 고 생각될 경우, '진행 정보'를 Map 으로 구성하는 것도 좋다. 이런 목적에 딱 맞는 Map 클래스가 ZNsMain::CKeyValueMap_T<> 이다. ■ 다수의 작업쓰레드에서 접근하는 설정 정보가 있고, 이 설정정보를 업데이트 하는 상황을 생각해보자. 이 설정 정보에는 동적할당 object 가 있어서 작업 쓰레드는 이 설정정 보가 업데이트 되는 동안에는, 이 정보에 접근하면 안된다. 그렇다고 매번 동기화 영역에서 설 정 정보가 업데이트 중인지 체크하는 것은 비합리적이므로 차라리 작업 쓰레드마다 설정 정보를 별도로 갖고 있는 것도 한 가지 방법일 듯 하다. 그래서 추가로 volatile 설정 정 보 업데이트 버전 변수를 하나 두고 계속 체크해서, 이 값이 변할 때만 동기화 영역에서 설정 정보를 업데이트하는 것이다. (물론 작업쓰레드 외부에 업데이트된 설정 정보가 이 미 존재해야 한다.) volatile 설정정보 업데이트 버전 변수를 두는 방법 이외에 설정 정 보를 업데이트하라는 표시를 가진 작업을 작업 쓰레드에 전달하는 것도 생각할 수 있다. -- 2009-12-23 00:24:00 '업데이트하라는 표시를 가진 작업을 작업쓰레드에 전달하는 것'이 아주 유력한 방법이 다. N 개의 쓰레드로 이루어진 쓰레드풀이라면 최소 N 개의 '업데이트 표시'를 작업 쓰레 드풀에 등록할 것이고, 풀의 각 작업 쓰레드가 이 '표시'를 작업큐에서 하나씩 꺼내와서, 업데이트가 시작될 수 있음을 알게 되는 때부터는 설정 정보에 접근할 때, 반드시 동기화 영역에서 접근한다. N 개의 쓰레드 전부가 이 표시를 받았음을 확인했으면 그때 비로서 설정 정보 업데이트 작업을 (물론 동기화 영역에서) 진행한다. 이때 N 개의 각 작업 쓰레 드는 이미 동기화 영역에서 설정 정보에 접근하고 있으므로, 설정 정보 업데이트 작업은 안전하다. 업데이트 설정 작업이 끝났으면, 그 이후로는 각 작업 쓰레드는 비동기화 영역 에서 설정 정보에 접근할 수 있다. 이런 식의 처리에는 주의점이 있는데, 쓰레드풀의 작업 쓰레드가 작업큐에서, 1 개의 작 업이 아닌 N 개의 작업을 가지고 올때는 도데체 몇 개의 '업데이트 표시'를 쓰레드풀에 등록해야 할 것인지 고민이 필요하다. 다수의 작업 쓰레드에서 접근하는 설정 정보를 정확하게 업데이트하는, 이와 같은 멀티쓰 레딩 디자인 패턴을 Configuration State Change Pattern 이라고 부르자. -- 2010-08-19 23:59:00 ■ 멀티쓰레딩 환경의 특정 작업쓰레드 모델에서 작업을 정의할 때, 작업 쓰레드에서 수행되 는 다수의 작업이 하나의 object 와 관련이 있고, 이 다수의 작업이 해당 object 의 멤버 에 접근해서 어떤 설정을 하는 경우, 게다가 이 설정 상태에 따라 object 에 관한 어떤 실행을 해야 하고(즉 그 object 의 특정 멤버 함수를 호출해야 하고), 그것도 동기화 영 역에서 해야 하는 경우, 해당 동기화 영역이 자칫 길어질 수 있다. 이를 방지하기 위해 해당 멤버함수의 수행을 비동기적으로(즉 별도의 작업쓰레드에서) 처리하는 것이 매우 좋 을 수 있다(이때 해당 object 가 해당 함수의 수행 전후로, 그 함수를 수행 중인지 아니 면 수행을 완료했는지를 나타내는 멤버 변수가 하나 필요하고 이 변수의 설정은 동기화 영역에서 이루어져야 한다. 그리고 일반적으로 이 멤버 변수가 '수행 중'임을 표시하면 다른 쓰레드에서 같은 object 에 대하여, 같은 함수의 수행을 막아야 한다). 해당 동기화 영역이 (특히 리눅스에서) 얼마나 비싼 구간인지, www.hemose.co.kr 의 주식 정보방 메신 저 서버의 작업쓰레드가 Dead Lock 에 빠지는 경험을 하면서 뼈져리게 느꼈다. -- 사실은 Dead Lock 보다는 무한 루프에 가까웠다. -- 2013-07-19 13:36:00 아니면 특정 상태의 object 만을 처리하는 '작업'을 정의할 수도 있겠다. epoll 을 사용 하여 IOCP 를 구현할 때, '소켓 종료' 작업을 별도의 이벤트로 정의했었다. -- 2010-01-05 00:42:00 그러고 보면 CSockInfo_BASE::OnInit(~) 는 상당히 초기에 동기화 영역 외부에서 호출하게 설계하였고, CSockInfo_BASE::OnRecvAll(~), CSockInfo_BASE::OnSendAll(~) 은 상당히 나중에 비동기적 기법으로 동기화 영역 외부에서 호출하게 했고 CSockInfo_BASE::OnClose(~) 는 이제서야 비동기적 기법으로 동기화 영역 외부에서 호출하게 했다. 휴우~~ 여기까지 장장 3 년의 세월이 흐른 것 같다. !! -- 2010-01-05 12:52:00 3 년이나 지나서야 비동기적으로 처리해야 할 부분을 (사실상) 모두 처리했다라는게, 어 떻게 보면 암울할 수 있다. 그런데, CSockInfo_BASE::OnClose(~) 에 동기화 영역에서 수 행된다라는 약점을 확실하게 인식하지 못한채, 분산처리 등 복잡한 소프트웨어를 개발했 었더라면 더 큰일이 발생하지 않았을까. 또한 이 약점을 발견한 시점이 막 분산처리에 이 라이브러리를 사용하려는 시점이있고, 때마침 www.hemose.co.kr 의 주식정보방 서버가 먹 통(Dead Lock) 이 되는 바람에, 이를 조사하는 과정에서 발견한 것이므로 상당한 행운이 라고 봐야 한다. 음, 그렇다면 나폴레옹이 오스터리츠 승리 후에 내린 포고령에서처럼 "장병들이여! 나는 그대들에게 만족하노라." 나에게 행운을 선사한, 그래서 나를 지켜준 장병들에게 감사를 표현하고 싶다. -- 2010-01-10 16:14:00 ■ 이 시점에서 멀티쓰레드 프로그래밍은 작업쓰레드 모델만 잘 설계하면 되는 줄 알았는데, 그 이외에 '작업'을 설계하는 것도 아주 중요한 것임을 알았다. 즉 다수의 작업쓰레드에 서 수행되는 '작업' 이 동일한 object 에 대하여 '작업'을 하는 것이고, '작업' 진행 상 태에 따라, 해당 object 의 특정 '작업'은 동기화 영역에서 보호를 해주어야 할 때, 게 다다 이 동기화 영역이 다소 길어질 수 있을 때, 이 '작업'을 비동기적으로 처리하도록 설계하는 것이 아주 중요하다는 깨달음을 얻었다. 이런 경우, 비동기적으로 처리해야 하는 작업 마다 그 '작업'이 처리 중인지, 완료되었는 지를 나타내는 멤버변수가 하나 필요하고, 이 멤버 변수의 설정은 그 '작업'의 전후에서 '동기화 영역'에서 수행되어야 하고, 이 변수가 '처리 중' 표시이면 다른 작업 쓰레드에 서 같은 object 에 대하여 같은 '작업' 의 수행을 (일반적으로) 막아야 한다. 그러면 비 동기적으로 수행했지만 동기화 영역에서 수행한 것과 같은 효과를 갖는 것이다. 이렇게 복잡한 상황은 좀처럼 없겠지만 IOCP 소켓 설계에서는 이처럼 복잡했다. -- 2010-01-05 13:10:00 ■ 다시 한 번 정리하면 동기화 영역에서 호출되어야 하는 함수는 수행 시간이 정말 짧은 지 확인해야 한다. 만약 이 함수가 상속등의 이유로 재정의되고, 다소 긴 작업을 하게 된다면, 이 함수의 수행을 비동기적으로 처리하는 것이 필수다. 동기화 영역에서 파일에 로그를 찍 는 것도 부담이 되는 작업이다. 멀티쓰레딩 관련 격언에 "항상 술어를 테스트하라! 그리고 다시 한 번 테스트하라!" 는 말이 있다(Posix 쓰레드를 이용한 프로그래밍, 124P, 데이비드 R. 부트노프, 정보문화사). 여기에 다른 격언을 추가하면 "동기화 영역에서 수행되는 함수가 정말 빨리 수행을 마치는 지 확인하라!" -- 2010-01-05 19:26:00 상황을 정리할 겸, 이런 경우을 가정해보자. 작업 쓰레드풀 에서 수행되는 작업 클래스 CSomeWork 와, 동기화 영역 sync1 에서 CSomeWork 을 처리하는 함수 func_sync_a (CSomeWork&) func_sync_b1(CSomeWork&) func_sync_b2(CSomeWork&) 그리고, 비동기화 영역에서 수행되는 함수 func_sync_not_b(CSomeWork&) 이 있다. 이 함수들은, CSomeWork 의 인스턴스 my_CSomeWork 에 대하여 func_sync_b1(my_CSomeWork) 을 호출했다면 다음에는 반드시 func_sync_not_b() 와 func_sync_b2() 를 순서대로 호출해야 한다. 즉 아래 호출 순서가 성립해야 한다. func_sync_b1 (my_CSomeWork) -- 함수1 func_sync_not_b(my_CSomeWork) -- 함수2 func_sync_b2 (my_CSomeWork) -- 함수3 func_sync_a(my_CSomeWork) 는 어디에서든지 호출될 수 있다. 이 상황에서 어떻게 '함수1' 부터 '함수3' 까지의 호출 순서를 유지할 수 있을까. 쉽게 처리할 거면, func_sync_b1() 부터 '함수3' 까지를 아예 같은 동기화 영역 sync1 에서 수행하는 것이다. (불행히도 이 경우, 만약 func_sync_not_b(my_CSomeWork) 가 func_sync_a(my_CSomeWork) 을 수행하는 경우, 같은 동기화 영역을 사용하고 있으므로 Dead Lock 에 빠질 수 있다.) 아니면 새로운 동기화 영역 sync2 에서 수행하게 하는 방법도 있다. 어느 방법이든지 func_ sync_not_b() 가 매우 길어질 수 있는 작업을 하는 경우에는, 이 동기화 영역을 수행하고 있 는 쓰레드는 어쩔수 없이 다른 쓰레드의 진입을 막고 있을 수 밖에 없다. 이런 사태는 어쨌든 피해야 한다. 피하는 방법은 간단하다고 볼 수 있다. CSomeWork 에 이 클래스의 인스턴스가 func_sync_b1() 를 수행하고 있는지, func_sync_b2() 을 수행하고 있는지를 나타내는 어떤 정수형 상태값 mi_func_state 을 두고, func_sync_b1() 의 수행이 끝나는 부분에서, mi_func_state 멤버 에 func_sync_b1() 을 수행했다는 표시를 설정하고, 작업 쓰레드 풀에, my_CSomeWork 에 대하여 func_sync_not_b(my_CSomeWork) 와 func_sync_b2() 을 잇달아 수행하게 하는 어떤 표시를 등록하는 것이다. 막 등록을 마치고, 동기화 영역을 벗어나면, 설령 다른 쓰레드가 my_CSomeWork 에 대하여 func_sync_b1(my_CSomeWork) 을 호출할려고 해도, 이미 my_CSomeWork. mi_func_state 에, func_sync_b1() 의 수행을 완료하고, 곧바로 func_sync_not_b(my_CSomeWork) 를 수행하려고 하고 있다는 표시가 들어있으므로, func_sync_b1(my_CSomeWork) 의 수행을 중지하고, 다른 CSomeWork 인스턴스에 대한 작업을 진행할 것이다. IOCP 소켓 관련 소스 CIOCP.H 나 CIOCPEPoll1.H, 혹은 CSockInfo_BASE_Win.H 나 CSockInfo_BASE_Linux.H 등에서 CSockInfo_BASE_T<>::OnInit() CSockInfo_BASE_T<>::OnRecvAll() CSockInfo_BASE_T<>::OnSendAll() CSockInfo_BASE_T<>::OnClose() 함수를 참고하면 된다. 이와 같이 해결하는 멀티쓰레딩 디자인 패턴을 Sequential Sync Pattern 이라고 부르자. -- 2010-08-19 23:59:00 ■ 헤모스(www.hemose.co.kr) 실시간 주식 정보방 메신저 서버가 갑자기 먹통이 되는 사태를 처리하면서, CSockInfo::OnClose(~) 를 비동기적으로 호출하는 것, 그리고 다른 약간의 오류 수정 및 몇몇 최적화 작업 이외에, 중대한 버그를 하나 잡아냈는데, CNetBuffSend_T <>::Fini() 함수에서 송신버퍼 리스트를 초기화하는데, for(int i=0;mo_CNetBuffSendList.size();++i) { // some code } 처럼 되어 있었다. 원래는 for(int i=0;i