1775 lines
68 KiB
C++
1775 lines
68 KiB
C++
|
||
|
||
#ifndef __ZCPPMAIN__PROCESS_H__
|
||
#define __ZCPPMAIN__PROCESS_H__
|
||
|
||
|
||
#include <string>
|
||
#include "ZCppMain/ZMainHead.H"
|
||
|
||
#ifdef _DEBUG_CEXCEPT_EXIT_
|
||
#include <iostream>
|
||
#endif
|
||
|
||
namespace ZNsMain
|
||
{
|
||
|
||
namespace ZNsIFace
|
||
{
|
||
|
||
class ZISyncEasy
|
||
{
|
||
public:
|
||
bool Lock (){return false;}
|
||
bool UnLock(){return false;}
|
||
public:
|
||
};/*
|
||
class ZISyncEasy*/
|
||
|
||
}/*
|
||
namespace ZNsIFace*/
|
||
|
||
|
||
template<typename TString> 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="<<AP_FileName<<", LineNum="<<AI_LineNum<<", AI_ErrNum="<<AI_ErrNum<<std::endl; ::exit(0);
|
||
#endif //_DEBUG_CEXCEPT_EXIT_
|
||
}/*
|
||
ZtCExceptSync(const char* AP_FileName, int AI_LineNum=0, int AI_ErrNum=0)*/
|
||
|
||
int GetLineNum() const{return mi_LineNum;}
|
||
int GetErrNum () const{return mi_ErrNum ;}
|
||
|
||
const TString& GetFileName() const
|
||
{
|
||
return mo_FileName;
|
||
}/*
|
||
const TString& GetFileName() const*/
|
||
|
||
public:
|
||
};/*
|
||
template<typename TString> class ZtCExceptSync : public ZNsMain::ZCExceptBase*/
|
||
|
||
|
||
typedef ZtCExceptSync<std::string> ZCExceptSync;
|
||
|
||
|
||
/*///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
■ CWorkPool.H 파일은 작업쓰레드 모델과 기타 동기화 자원 관련 클래스 템플릿이 들어 있는데,
|
||
단일 코어나 단일 쓰레드 환경의 경우에 굳이 동기화 object 가 필요하지 않는 경우가 있다.
|
||
(특히 Easy 로 끝나는 동기화 템플릿에서) 이때 인터페이스를 해치지 않고 사용할 수 있도록
|
||
ZtCSyncEasyValid<> 와 CSyncEasyEmpty_T<> 을 설계한다.
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////*/
|
||
|
||
|
||
template<typename TSyncEasy> class ZtCSyncEasyValid
|
||
{
|
||
public :
|
||
enum{EUseLockObj=1};
|
||
protected:
|
||
TSyncEasy mo_CSyncEasy;
|
||
public :
|
||
void Lock (){mo_CSyncEasy.Lock ();}
|
||
void UnLock(){mo_CSyncEasy.UnLock();}
|
||
public :
|
||
};/*
|
||
template<typename TSyncEasy> class ZtCSyncEasyValid */
|
||
|
||
template<typename TSyncEasy=ZNsMain::ZCEmpty> class CSyncEasyEmpty_T
|
||
{
|
||
public:
|
||
enum{EUseLockObj=0};
|
||
public:
|
||
void Lock (){}
|
||
void UnLock(){}
|
||
public:
|
||
};/*
|
||
template<typename TSyncEasy=ZNsMain::ZCEmpty> class CSyncEasyEmpty_T */
|
||
|
||
}/*
|
||
namespace ZNsMain*/
|
||
|
||
|
||
#ifdef _WIN
|
||
#include "ZCppMain/ZCProcess_Win.H"
|
||
#elif defined(__linux__)
|
||
#include "ZCppMain/ZCProcess_Linux.H"
|
||
#else
|
||
#include "ZCppMain/ZCProcess_Linux.H"
|
||
#endif
|
||
|
||
|
||
namespace ZNsMain
|
||
{
|
||
|
||
/*/////////////////////////////////////////////////////////////
|
||
|
||
■ class ZtCSyncCount 는 내부 lock 카운트를 가짐으로써
|
||
해당 세마포어에 몇 개의 쓰레드가 대기하고 있는지 알 수 있다.
|
||
음수이면 그 절대값이 대기하고 있는 쓰레드 갯수이다.
|
||
0 이상이면 대기하고 있는 쓰레드가 없는 것이다.
|
||
|
||
■ lock 을 걸때마다 내부 lock 카운트가 1 감소하고
|
||
lock 을 풀때마다 내부 lock 카운트가 1 증가한다.
|
||
|
||
/////////////////////////////////////////////////////////////*/
|
||
|
||
template< typename TSemaphore=ZNsMain::ZCThreadSemaphore
|
||
>
|
||
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<typename TSyncExec>
|
||
bool LockCount(TSyncExec& AR_CSyncExec)
|
||
{
|
||
--mi_LockCount;
|
||
|
||
AR_CSyncExec.UnLock();
|
||
{
|
||
if(this->TSemaphore::Lock()==false) return false;
|
||
}
|
||
AR_CSyncExec.Lock();
|
||
|
||
return false;
|
||
}/*
|
||
template<typename TSyncExec>
|
||
bool LockCount(TSyncExec& AR_CSyncExec) */
|
||
|
||
|
||
// cf) linux 의 semctl(), semop() 계열의 세마포어에서는 LockTime() 함수가 없다.
|
||
|
||
template<typename TSyncExec>
|
||
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<typename TSyncExec>
|
||
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<typename TSyncExec>
|
||
bool LockCount(TSyncExec& AR_CSyncExec)
|
||
{
|
||
--mi_LockCount;
|
||
|
||
AR_CSyncExec.UnLock();
|
||
{
|
||
if(mr_CSemaphore.Lock()==false) return false;
|
||
}
|
||
AR_CSyncExec.Lock();
|
||
|
||
return true;
|
||
}/*
|
||
template<typename TSyncExec>
|
||
bool LockCount(TSyncExec& AR_CSyncExec) */
|
||
|
||
template<typename TSyncExec>
|
||
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<typename TSyncExec>
|
||
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 <class S> template <class U> friend class A<S>::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 <THelpKey1, THelpKey2>(AR_HelpKey1, AR_HelpKey2)
|
||
this->CThreadBase::template Fini <THelpKey1, THelpKey2>(AR_HelpKey1, AR_HelpKey2)
|
||
|
||
가 아니라,
|
||
|
||
this->CThreadBase::template Init2<THelpKey1, THelpKey2>(AR_HelpKey1, AR_HelpKey2)
|
||
this->CThreadBase::template Fini2<THelpKey1, THelpKey2>(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<typename THelpKey> 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<THelpKey>(mr_HelpKey);}
|
||
void Exec(){mp_CDerive->template Exec<THelpKey>(mr_HelpKey);}
|
||
void Fini(){mp_CDerive->template Fini<THelpKey>(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<typename THelpKey> class ZtCHelpKeyParam */
|
||
|
||
|
||
template<typename THelpKey1, typename THelpKey2> 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<THelpKey1, THelpKey2>(mr_HelpKey1, mr_HelpKey2);}
|
||
void Exec2(){mp_CDerive->template Exec2<THelpKey1, THelpKey2>(mr_HelpKey1, mr_HelpKey2);}
|
||
void Fini2(){mp_CDerive->template Fini2<THelpKey1, THelpKey2>(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<typename THelpKey1, typename THelpKey2> 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<THelpKey1, THelpKey2, THelpKey3>(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);}
|
||
void Exec3(){mp_CDerive->template Exec3<THelpKey1, THelpKey2, THelpKey3>(mr_HelpKey1, mr_HelpKey2, mr_HelpKey3);}
|
||
void Fini3(){mp_CDerive->template Fini3<THelpKey1, THelpKey2, THelpKey3>(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()"<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
_VT_ void Init()*/
|
||
|
||
template<typename THelpKey> _VT_ void Init(THelpKey)
|
||
{
|
||
this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다.
|
||
|
||
#ifdef _DEBUG
|
||
std::cout<<"# ZCThreadEx::Init(THelpKey), THelpKey typename="<<typeid(THelpKey).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey> _VT_ void Init(THelpKey)*/
|
||
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Init(THelpKey1, THelpKey2)
|
||
{
|
||
this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다.
|
||
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Init(THelpKey1, THelpKey2), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Init(THelpKey1, THelpKey2)*/
|
||
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
_VT_ void Init(THelpKey1, THelpKey2, THelpKey3) /*##################*/
|
||
{
|
||
this->TThread::Detach(); // linux 에서는 아주 중요한 코드이다.
|
||
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Init(THelpKey1, THelpKey2, THelpKey3), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<", "
|
||
"THelpKey3 typename="<<typeid(THelpKey3).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
_VT_ void Init(THelpKey1, THelpKey2, THelpKey3) ####################*/
|
||
|
||
|
||
_VT_ void Exec()
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"ZCThreadEx::Exec()"<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
_VT_ void Exec()*/
|
||
|
||
template<typename THelpKey> _VT_ void Exec(THelpKey)
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"# ZCThreadEx::Exec(THelpKey), THelpKey typename="<<typeid(THelpKey).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey> _VT_ void Exec(THelpKey)*/
|
||
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Exec(THelpKey1, THelpKey2)
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Exec(THelpKey1, THelpKey2), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Exec(THelpKey1 AR_HelpKey1,THelpKey2 AR_HelpKey2)*/
|
||
|
||
template<typename THelpKey1, typename THelpKey2, typename THelpKey3>
|
||
_VT_ void Exec(THelpKey1 AR_HelpKey1,THelpKey2 AR_HelpKey2,THelpKey3 AR_HelpKey3)
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Exec(THelpKey1, THelpKey2, THelpKey3), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<", "
|
||
"THelpKey3 typename="<<typeid(THelpKey3).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2, typename THelpKey3>
|
||
_VT_ void Exec(THelpKey1 AR_HelpKey1,THelpKey2 AR_HelpKey2,THelpKey3 AR_HelpKey3) */
|
||
|
||
|
||
_VT_ void Fini()
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"ZCThreadEx::Fini()"<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
_VT_ void Fini()*/
|
||
|
||
template<typename THelpKey> _VT_ void Fini(THelpKey)
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"# ZCThreadEx::Fini(THelpKey), THelpKey typename="<<typeid(THelpKey).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey> _VT_ void Fini(THelpKey) */
|
||
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Fini(THelpKey1, THelpKey2)
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Fini(THelpKey1, THelpKey2), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2> _VT_ void Fini(THelpKey1, THelpKey2)*/
|
||
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
_VT_ void Fini(THelpKey1, THelpKey2, THelpKey3) //////////////////////
|
||
{
|
||
#ifdef _DEBUG
|
||
std::cout<<"## ZCThreadEx::Fini(THelpKey1, THelpKey2, THelpKey3), "
|
||
"THelpKey1 typename="<<typeid(THelpKey1).name()<<", "
|
||
"THelpKey2 typename="<<typeid(THelpKey2).name()<<", "
|
||
"THelpKey3 typename="<<typeid(THelpKey3).name()<<std::endl;
|
||
#endif //_DEBUG
|
||
}/*
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
_VT_ void Fini(THelpKey1, THelpKey2, THelpKey3) ////////////////////*/
|
||
|
||
|
||
static _THREAD_RETURN_ ThreadFunc(void* AP_Void)
|
||
{
|
||
ZCDerive* VP_CDerive = (ZCDerive*)AP_Void;
|
||
|
||
VP_CDerive->Init();
|
||
VP_CDerive->Exec();
|
||
VP_CDerive->Fini(); return 0; ///////////
|
||
}/*
|
||
static _THREAD_RETURN_ ThreadFunc(void* AP_Void)*/
|
||
|
||
template<typename THelpKey>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey(void* AP_Void)
|
||
{
|
||
typedef ZtCHelpKeyParam<THelpKey> ZCHelpKeyParam;
|
||
|
||
ZCHelpKeyParam* VP_CHelpKeyParam = (ZCHelpKeyParam*)AP_Void;
|
||
|
||
VP_CHelpKeyParam->Init();
|
||
VP_CHelpKeyParam->Exec();
|
||
VP_CHelpKeyParam->Fini(); delete VP_CHelpKeyParam; return 0;
|
||
}/*
|
||
template<typename THelpKey>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey(void* AP_Void) */
|
||
|
||
template<typename THelpKey1, typename THelpKey2>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey2(void* AP_Void)
|
||
{
|
||
typedef ZtCHelpKeyParam2<THelpKey1, THelpKey2> CHelpKeyParam2;
|
||
|
||
CHelpKeyParam2* VP_CHelpKeyParam2 = (CHelpKeyParam2*)AP_Void ;
|
||
|
||
#if(_CODE_OLD_)
|
||
VP_CHelpKeyParam2->Init2();
|
||
VP_CHelpKeyParam2->Exec2();
|
||
VP_CHelpKeyParam2->Fini2();
|
||
#else
|
||
VP_CHelpKeyParam2->Init ();
|
||
VP_CHelpKeyParam2->Exec ();
|
||
VP_CHelpKeyParam2->Fini ();
|
||
#endif
|
||
|
||
delete VP_CHelpKeyParam2; return 0;
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey2(void* AP_Void) */
|
||
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey3(void* AP_Void) /////////////
|
||
{
|
||
typedef ZtCHelpKeyParam3
|
||
<THelpKey1, THelpKey2, THelpKey3> CHelpKeyParam3;
|
||
|
||
CHelpKeyParam3* VP_CHelpKeyParam3 = (CHelpKeyParam3*)AP_Void;
|
||
|
||
#if(_CODE_OLD_)
|
||
VP_CHelpKeyParam3->Init3();
|
||
VP_CHelpKeyParam3->Exec3();
|
||
VP_CHelpKeyParam3->Fini3();
|
||
#else
|
||
VP_CHelpKeyParam3->Init ();
|
||
VP_CHelpKeyParam3->Exec ();
|
||
VP_CHelpKeyParam3->Fini ();
|
||
#endif
|
||
|
||
delete VP_CHelpKeyParam3; return 0;
|
||
}/*
|
||
template< typename THelpKey1, typename THelpKey2, typename THelpKey3
|
||
>
|
||
static _THREAD_RETURN_ ThreadFuncHelpKey3(void* AP_Void) ///////////*/
|
||
|
||
|
||
ZCDerive* GetCDerivePtr()
|
||
{
|
||
return static_cast<ZCDerive*>(this);
|
||
}/*
|
||
ZCDerive* GetCDerivePtr()*/
|
||
|
||
/*protected:*/
|
||
public :
|
||
|
||
ZtCThreadEx()
|
||
{
|
||
_DEBUG_REENTRANT_CHECK_
|
||
}/*
|
||
ZtCThreadEx()*/
|
||
|
||
ZtCThreadEx(const ZtCThreadEx& rhs)
|
||
{
|
||
_DEBUG_REENTRANT_CHECK_
|
||
|
||
(TThread&)(*this)=(TThread&)rhs;
|
||
}/*
|
||
ZtCThreadEx(const ZtCThreadEx& rhs)*/
|
||
|
||
ZtCThreadEx& operator=(const ZtCThreadEx& rhs)
|
||
{
|
||
return *this; // nothig to do
|
||
}/*
|
||
ZtCThreadEx& operator=(const ZtCThreadEx& rhs)*/
|
||
|
||
|
||
virtual ~ZtCThreadEx(){}
|
||
|
||
|
||
bool Make()
|
||
{
|
||
return this->TThread::Make(ThreadFunc, GetCDerivePtr());
|
||
}/*
|
||
bool Make()*/
|
||
|
||
|
||
template<typename THelpKey> bool Make(THelpKey AR_HelpKey)
|
||
{
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey> ZCCheckRef ;
|
||
typedef typename ZCCheckRef::TypeData TypeData ;
|
||
typedef ZtCHelpKeyParam<TypeData> ZCHelpKeyParam;
|
||
|
||
ZCHelpKeyParam* VP_CHelpKeyParam = new ZCHelpKeyParam
|
||
(
|
||
ZCCheckRef::PassData(AR_HelpKey), *GetCDerivePtr()
|
||
);
|
||
/*:::::::::::::::::::::::::::::::::::::::::::::::::::*/
|
||
|
||
return this->TThread::Make(
|
||
&ZtCThreadEx::template ThreadFuncHelpKey
|
||
<
|
||
TypeData
|
||
> ,
|
||
VP_CHelpKeyParam
|
||
/*/////////*/ ); ///////////////////////////////////////
|
||
}/*
|
||
template<typename THelpKey> bool Make(THelpKey AR_HelpKey) */
|
||
|
||
|
||
template<typename THelpKey1, typename THelpKey2>
|
||
bool Make(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2)
|
||
{
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey1> ZCCheckRef1;
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey2> ZCCheckRef2;
|
||
|
||
typedef typename ZCCheckRef1::TypeData TypeData1 ;
|
||
typedef typename ZCCheckRef2::TypeData TypeData2 ;
|
||
|
||
typedef ZtCHelpKeyParam2<TypeData1, TypeData2> ZCHelpKeyParam;
|
||
|
||
ZCHelpKeyParam* VP_CHelpKeyParam = new ZCHelpKeyParam
|
||
(
|
||
ZCCheckRef1::PassData(AR_HelpKey1),
|
||
ZCCheckRef2::PassData(AR_HelpKey2),
|
||
*GetCDerivePtr()
|
||
);
|
||
/////////////////////////////////////////////////////
|
||
|
||
return this->TThread::Make
|
||
(
|
||
&ZtCThreadEx::template
|
||
ThreadFuncHelpKey2<TypeData1, TypeData2>,
|
||
VP_CHelpKeyParam
|
||
);
|
||
/*:::::::::::::::::::::::::::::::::::::::::::::::::*/
|
||
}/*
|
||
template<typename THelpKey1, typename THelpKey2>
|
||
bool Make(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2) */
|
||
|
||
|
||
template
|
||
<typename THelpKey1 , typename THelpKey2 , typename THelpKey3 >
|
||
bool Make
|
||
(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, THelpKey3 AR_HelpKey3)
|
||
{
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey1> ZCCheckRef1;
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey2> ZCCheckRef2;
|
||
typedef ZNsMain::ZtCCheckRef<THelpKey3> ZCCheckRef3;
|
||
|
||
typedef typename ZCCheckRef1::TypeData TypeData1 ;
|
||
typedef typename ZCCheckRef2::TypeData TypeData2 ;
|
||
typedef typename ZCCheckRef3::TypeData TypeData3 ;
|
||
|
||
typedef ZtCHelpKeyParam3
|
||
<TypeData1, TypeData2, TypeData3> ZCHelpKeyParam ;
|
||
|
||
ZCHelpKeyParam* VP_CHelpKeyParam = new ZCHelpKeyParam
|
||
(
|
||
ZCCheckRef1::PassData(AR_HelpKey1),
|
||
ZCCheckRef2::PassData(AR_HelpKey2),
|
||
ZCCheckRef3::PassData(AR_HelpKey3),
|
||
*GetCDerivePtr()
|
||
);
|
||
/////////////////////////////////////////////////////
|
||
|
||
return this->TThread::Make /*::::::::::::::::::::::*/
|
||
(
|
||
&ZtCThreadEx::template
|
||
ThreadFuncHelpKey3<TypeData1, TypeData2, TypeData3> ,
|
||
VP_CHelpKeyParam
|
||
);
|
||
/*:::::::::::::::::::::::::::::::::::::::::::::::::*/
|
||
}/*
|
||
template
|
||
<typename THelpKey1 , typename THelpKey2 , typename THelpKey3 >
|
||
bool Make
|
||
(THelpKey1 AR_HelpKey1, THelpKey2 AR_HelpKey2, THelpKey3 AR_HelpKey3)
|
||
*/
|
||
|
||
public:
|
||
};/*
|
||
template< typename TDerive, typename TThread=ZtCThread<>
|
||
>
|
||
class ZtCThreadEx //////////////////////////////////////*/
|
||
|
||
|
||
/*//////////////////////////////////////////////////////////////////
|
||
|
||
■ 동기화 object 와 그 object 의 동기화 영역에서 보호되어야 하는 데
|
||
이타를 한 개의 클래스에 넣어 두면 운용하기 편할 것이다.
|
||
|
||
//////////////////////////////////////////////////////////////////*/
|
||
|
||
template< typename TSyncData ,
|
||
typename TDataArg =const TSyncData& ,
|
||
typename TSyncEasy=ZNsMain::ZCCriticSectEasy
|
||
>
|
||
class ZtCSyncData : public TSyncEasy /*###############*/
|
||
{
|
||
public :
|
||
typedef TSyncData TypeData;
|
||
typedef TDataArg TypeArg ;
|
||
typedef TSyncEasy TypeSync;
|
||
public :
|
||
typedef ZNsMain::ZtCAutoKey<TSyncEasy> CAutoKey;
|
||
protected:
|
||
TSyncData mo_CSyncData;
|
||
public :
|
||
|
||
ZtCSyncData(){}
|
||
|
||
ZtCSyncData(TDataArg AR_CDataArg)
|
||
{
|
||
mo_CSyncData=AR_CDataArg;
|
||
}/*
|
||
ZtCSyncData(TDataArg AR_CDataArg)*/
|
||
|
||
ZtCSyncData(const ZtCSyncData& rhs)
|
||
{
|
||
mo_CSyncData=rhs.mo_CSyncData;
|
||
}/*
|
||
ZtCSyncData(const ZtCSyncData& rhs)*/
|
||
|
||
ZtCSyncData& operator=(const ZtCSyncData& rhs)
|
||
{
|
||
mo_CSyncData=rhs.mo_CSyncData; return *this;
|
||
}/*
|
||
ZtCSyncData& operator=(const ZtCSyncData& rhs)*/
|
||
|
||
ZtCSyncData& operator=(TDataArg AR_CDataArg)
|
||
{
|
||
mo_CSyncData=AR_CDataArg; return *this;
|
||
}/*
|
||
ZtCSyncData& operator=(TDataArg AR_CDataArg)*/
|
||
|
||
|
||
TSyncData& GetData()
|
||
{
|
||
return mo_CSyncData;
|
||
}/*
|
||
TSyncData& GetData()*/
|
||
|
||
const TSyncData& GetData() const
|
||
{
|
||
return mo_CSyncData;
|
||
}/*
|
||
const TSyncData& GetData() const*/
|
||
|
||
operator TSyncData& ()
|
||
{
|
||
return mo_CSyncData;
|
||
}/*
|
||
operator TSyncData& ()*/
|
||
|
||
operator const TSyncData& () const
|
||
{
|
||
return mo_CSyncData;
|
||
}/*
|
||
operator const TSyncData& () const*/
|
||
|
||
public:
|
||
};/*
|
||
template< typename TSyncData ,
|
||
typename TDataArg =const TSyncData& ,
|
||
typename TSyncEasy=ZNsMain::ZCCriticSectEasy
|
||
>
|
||
class ZtCSyncData ////////////////////////////////////*/
|
||
|
||
|
||
/*/////////////////////////////////////////////////////////
|
||
|
||
■ 임계 영역에서 보호하려는 데이타가 특히 object 인 경우에
|
||
약간 더 특화된 클래스 템플릿이다.
|
||
|
||
/////////////////////////////////////////////////////////*/
|
||
|
||
template< typename TSyncData ,
|
||
typename TDataArg =const TSyncData& ,
|
||
typename TSyncEasy=ZNsMain::ZCCriticSectEasy
|
||
>
|
||
class ZtCSyncObj : public TSyncEasy, public TSyncData
|
||
{
|
||
public:
|
||
typedef TSyncData TypeData;
|
||
typedef TDataArg TypeArg ;
|
||
typedef TSyncEasy TypeSync;
|
||
public:
|
||
typedef ZNsMain::ZtCAutoKey<TSyncEasy> CAutoKey;
|
||
public:
|
||
|
||
#define __STATIC_BASE__(R) static_cast<TSyncData&>(R)
|
||
|
||
ZtCSyncObj(){}
|
||
|
||
ZtCSyncObj(TDataArg AR_CDataArg)
|
||
{
|
||
__CAST_BASE__(*this)= AR_CDataArg;
|
||
}/*
|
||
ZtCSyncObj(TDataArg AR_CDataArg)*/
|
||
|
||
ZtCSyncObj(const ZtCSyncObj& rhs)
|
||
{
|
||
__CAST_BASE__(*this)= __CAST_BASE__(rhs);
|
||
}/*
|
||
ZtCSyncObj(const ZtCSyncObj& rhs)*/
|
||
|
||
ZtCSyncObj& operator=(const ZtCSyncObj& rhs)
|
||
{
|
||
__CAST_BASE__(*this)= __CAST_BASE__(rhs); return *this;
|
||
}/*
|
||
ZtCSyncObj& operator=(const ZtCSyncObj& rhs)*/
|
||
|
||
ZtCSyncObj& operator=(TDataArg AR_CDataArg)
|
||
{
|
||
__CAST_BASE__(*this)= AR_CDataArg; return *this;
|
||
}/*
|
||
ZtCSyncObj& operator=(TDataArg AR_CDataArg)*/
|
||
|
||
#undef __CAST_BASE__
|
||
|
||
public:
|
||
};/*
|
||
template< typename TSyncData ,
|
||
typename TDataArg =const TSyncData& ,
|
||
typename TSyncEasy=ZNsMain::ZCCriticSectEasy
|
||
>
|
||
class ZtCSyncObj /////////////////////////////////////*/
|
||
|
||
|
||
|
||
#if(_CODE_OLD_)
|
||
|
||
/*/////////////////////////////////////////////////////////////////////////////
|
||
|
||
■ TMutexCondData 클래스를 상속하여 대기 상태에 관련된 2 개의 bool 멤버를 갖는다.
|
||
|
||
■ TMutexCondData 클래스 타입은 mutex 와 조건변수를 가지고 있어야 한다.
|
||
|
||
■ ZtCMutexCondDataVar 에 접근하는 쓰레드는 크게 2개가 있다.
|
||
|
||
하나는 ZtCMutexCondDataVar 를 가지고 있는 쓰레드로, 주로 작업이 없으면 잠드
|
||
는 쓰레드이고, 또 하나는 해야 할 작업이 발생했는데, ZtCMutexCondDataVar 에
|
||
서 잠들어 있는 쓰레드를 깨우는 쓰레드이다. 즉 아래로 정리하자.
|
||
|
||
1번 쓰레드 : 잠자는 쓰레드, 작업이 없으면 잠자는 쓰레드.
|
||
2번 쓰레드 : 깨우는 쓰레드, 작업이 있으면 깨우는 쓰레드.
|
||
|
||
-- 2025-10-04 18:18
|
||
|
||
/////////////////////////////////////////////////////////////////////////////*/
|
||
|
||
template< typename TMutexCondData=ZNsMain::ZtCMutexCondData<>
|
||
>
|
||
class ZtCMutexCondDataVar : public TMutexCondData /////////////
|
||
{
|
||
public :
|
||
typedef TMutexCondData TypeData ;
|
||
typedef TMutexCondData ZCMutexCondData;
|
||
protected:
|
||
bool mb_MustWait;
|
||
bool mb_WaitCond;
|
||
public :
|
||
|
||
ZtCMutexCondDataVar()
|
||
{
|
||
mb_MustWait= false;
|
||
mb_WaitCond= false;
|
||
}/*
|
||
ZtCMutexCondDataVar()*/
|
||
|
||
ZtCMutexCondDataVar(const ZtCMutexCondDataVar& rhs)
|
||
{
|
||
mb_MustWait= false;
|
||
mb_WaitCond= false;
|
||
}/*
|
||
ZtCMutexCondDataVar(const ZtCMutexCondDataVar& rhs)*/
|
||
|
||
ZtCMutexCondDataVar& operator=(const ZtCMutexCondDataVar& rhs)
|
||
{
|
||
return *this; // nothing to do.
|
||
}/*
|
||
ZtCMutexCondDataVar& operator=(const ZtCMutexCondDataVar& rhs)*/
|
||
|
||
|
||
_SY_ void WaitCondIfMust()
|
||
{
|
||
/* 이 TMutexCondData 에 접근하는 쓰레드(즉 '잠자는 쓰레드')는
|
||
|
||
this->mb_MustWait==true
|
||
|
||
인 경우, 해야 할 작업이 없는 것으로 판단하고, WaitCond()
|
||
서 대기한다. -- 2025-10-04 18:21
|
||
*/
|
||
this->TMutexCondData::Lock();
|
||
{
|
||
if(this->mb_MustWait==true)
|
||
{
|
||
this->mb_WaitCond=true ;
|
||
this->TMutexCondData::WaitCond();
|
||
this->mb_WaitCond=false;
|
||
}/*
|
||
if(this->mb_MustWait==true)*/
|
||
}
|
||
this->TMutexCondData::UnLock();
|
||
}/*
|
||
_SY_ void WaitCondIfMust()*/
|
||
|
||
|
||
_SY_ void WakeCondOnBool(bool AB_MustWait)
|
||
{
|
||
/*////////////////////////////////////////////////////////////////
|
||
|
||
■ 처리해야 할 작업이 발생해서 '깨우는 쓰레드'에서 호출한다.
|
||
|
||
-- 2025-10-04 18:23
|
||
|
||
■ 최초 이름은 SetMustWaitBool() 이었다.
|
||
|
||
AB_MustWait 와 mb_MustWait 에 따라, WakeCond() 을 호출하는데,
|
||
|
||
this->mb_MustWait==true && AB_MustWait==false, 즉
|
||
|
||
this->mb_MustWait==true 이 this->mb_MustWait==false
|
||
|
||
로 바뀌는 경우에, 쓰레드가 잠자고 있다면 깨운다. -- 2025-10-04 17:23
|
||
|
||
상태 this->mb_MustWait==true 를 만든 쓰레드는 '잠자는 쓰레드'이고
|
||
상태 this->mb_MustWait==false 를 만든 쓰레드는 '깨우는 쓰레드'이다.
|
||
|
||
다시 풀어 쓰면,
|
||
|
||
상태 this->mb_MustWait==true 를 만든 쓰레드는 '소비자 쓰레드'이고
|
||
상태 this->mb_MustWait==false 를 만든 쓰레드는 '생산자 쓰레드'이다.
|
||
|
||
-- 2025-10-06 18:29
|
||
|
||
////////////////////////////////////////////////////////////////*/
|
||
|
||
this->TMutexCondData::Lock();
|
||
{
|
||
if(AB_MustWait==this->mb_MustWait)
|
||
{
|
||
// nothing to do
|
||
}
|
||
else if(this->mb_MustWait=AB_MustWait)
|
||
{
|
||
/* nothing to do
|
||
|
||
this->mb_MustWait==false && AB_MustWait==true, 즉
|
||
|
||
this->mb_MustWait==false 이 this->mb_MustWait==true
|
||
|
||
로 바뀌는 경우. nothing to do.
|
||
*/
|
||
}
|
||
else if(this->mb_WaitCond==true)
|
||
{
|
||
/*
|
||
this->mb_MustWait==true && AB_MustWait==false, 즉
|
||
|
||
this->mb_MustWait==true 이 this->mb_MustWait==false
|
||
|
||
로 바뀌는 경우. 이때는 쓰레드가 잠자고 있다면 깨워야 한다.
|
||
*/
|
||
this->TMutexCondData::WakeCond();
|
||
this->mb_WaitCond=false;
|
||
}/*
|
||
else if(this->mb_WaitCond==true)*/
|
||
}
|
||
this->TMutexCondData::UnLock();
|
||
|
||
// WakeCondOnBool(true ) : '잠자는 쓰레드'에서 호출.
|
||
// WakeCondOnBool(false) : '깨우는 쓰레드'에서 호출.
|
||
}/*
|
||
_SY_ void WakeCondOnBool(bool AB_MustWait)*/
|
||
|
||
|
||
_SY_ void SetMustWaitTrue (){WakeCondOnBool(true );} // '잠자는 쓰레드'에서 호출.
|
||
_SY_ void SetMustWaitFalse(){WakeCondOnBool(false);} // '깨우는 쓰레드'에서 호출.
|
||
_SY_ void WakeCondOnTrue (){WakeCondOnBool(true );} // '잠자는 쓰레드'에서 호출.
|
||
_SY_ void WakeCondOnFalse (){WakeCondOnBool(false);} // '깨우는 쓰레드'에서 호출.
|
||
|
||
|
||
bool MustWait () const{return mb_MustWait;}
|
||
bool DoWaitCond() const{return mb_WaitCond;}
|
||
|
||
public:
|
||
};/*
|
||
template< typename TMutexCondData=ZNsMain::ZtCMutexCondData<>
|
||
>
|
||
class ZtCMutexCondDataVar ///////////////////////////////////*/
|
||
|
||
#endif //_CODE_OLD_
|
||
|
||
|
||
|
||
/*/////////////////////////////////////////////////////////////////////////////
|
||
|
||
■ TMutexCondData 클래스를 상속하여 대기 상태를 표시하는 1 개의 bool 멤버를 갖는다.
|
||
|
||
■ TMutexCondData 클래스 타입은 mutex 와 조건변수를 가지고 있어야 한다.
|
||
|
||
■ ZtCMutexCondVar 에 접근하는 쓰레드는 크게 2개가 있다.
|
||
|
||
하나는 ZtCMutexCondVar 를 가지고 있는 쓰레드로, 주로 작업이 없으면 잠드는
|
||
쓰레드이고, 또 하나는 해야 할 작업이 발생했는데, ZtCMutexCondDataVar 에서
|
||
잠들어 있는 쓰레드를 깨우는 쓰레드이다. 즉 아래로 정리하자.
|
||
|
||
1번 쓰레드 : 잠자는 쓰레드, 작업이 없으면 잠자는 쓰레드.
|
||
2번 쓰레드 : 깨우는 쓰레드, 작업이 있으면 깨우는 쓰레드.
|
||
|
||
-- 2025-10-04 19:07
|
||
|
||
바꿔 말하면,
|
||
|
||
1번 쓰레드는 '소비자 쓰레드'이고, 2번 쓰레드는 '생산자 쓰레드'
|
||
|
||
이다. 그래서
|
||
|
||
WaitCondBoolSync() 는 '소비자 쓰레드'에서 호출하고
|
||
WakeCondBoolSync() 는 '생산자 쓰레드'에서 호출한다.
|
||
|
||
-- 2025-10-06 18:38
|
||
|
||
/////////////////////////////////////////////////////////////////////////////*/
|
||
|
||
template< typename TMutexCondData=ZNsMain::ZtCMutexCondData<>
|
||
>
|
||
class ZtCMutexCondVar : public TMutexCondData /////////////////
|
||
{
|
||
public :
|
||
typedef TMutexCondData TypeData ;
|
||
typedef TMutexCondData ZCMutexCondData;
|
||
protected:
|
||
bool mb_WaitCond; // 지금 쓰레드가 작업이 없어서 잠잔다.
|
||
bool mb_WakeCond; // 어떤 쓰레드가 작업이 있어서 깨웠다.
|
||
public :
|
||
|
||
ZtCMutexCondVar()
|
||
{
|
||
mb_WaitCond= false;
|
||
mb_WakeCond= false;
|
||
}/*
|
||
ZtCMutexCondVar()*/
|
||
|
||
ZtCMutexCondVar(const ZtCMutexCondVar& rhs)
|
||
{
|
||
mb_WaitCond= false;
|
||
mb_WakeCond= false;
|
||
}/*
|
||
ZtCMutexCondVar(const ZtCMutexCondVar& rhs)*/
|
||
|
||
ZtCMutexCondVar& operator=(const ZtCMutexCondVar& rhs)
|
||
{
|
||
return *this; // nothing to do.
|
||
}/*
|
||
ZtCMutexCondVar& operator=(const ZtCMutexCondVar& rhs)*/
|
||
|
||
|
||
_SY_ bool WaitCondBoolSync()
|
||
{
|
||
/* 이 TMutexCondData 에 접근해 작업을 처리한 쓰레드
|
||
|
||
즉 잠자는 쓰레드, 소비자 쓰레드
|
||
|
||
는 작업이 끝나면 WaitCond() 에서 대기한다.
|
||
|
||
다만, 이 함수로 2개 이상의 쓰레드가 대기하는 것은
|
||
잘못된 설계이다. 어디까지나 1 개 쓰레드 대기용이다.
|
||
|
||
-- 2025-10-04 18:21
|
||
*/
|
||
bool VB_Return = false;
|
||
|
||
this->TMutexCondData::Lock();
|
||
{
|
||
if(!this->mb_WakeCond && !this->mb_WaitCond)
|
||
{
|
||
this->mb_WaitCond = true ;
|
||
this->TMutexCondData::WaitCond();
|
||
this->mb_WaitCond = false;
|
||
this->mb_WakeCond = false;
|
||
|
||
VB_Return = true;
|
||
}/*
|
||
if(!this->mb_WakeCond && !this->mb_WaitCond)*/
|
||
|
||
else // '작업'이 발생해 잠들지 않고 계속 작업하는 경우.
|
||
{
|
||
this->mb_WaitCond = false;
|
||
this->mb_WakeCond = false;
|
||
}/*
|
||
else // '작업'이 발생해 잠들지 않고 계속 작업하는 경우.*/
|
||
}
|
||
this->TMutexCondData::UnLock();
|
||
|
||
return VB_Return;
|
||
}/*
|
||
_SY_ bool WaitCondBoolSync()*/
|
||
|
||
|
||
_SY_ void WakeCondBoolSync()
|
||
{
|
||
/* 작업이 발생해, 이 TMutexCondData 에 접근해 해당
|
||
쓰레드를 깨워 작업을 시키려는 쓰레드
|
||
|
||
즉 깨우는 쓰레드, 생산자 쓰레드
|
||
|
||
가 호출한다. -- 2025-10-04 19:01
|
||
*/
|
||
this->TMutexCondData::Lock();
|
||
{
|
||
if(!this->mb_WakeCond)
|
||
{
|
||
this->TMutexCondData::WakeCond(); this->mb_WakeCond=true;
|
||
}/*
|
||
if(!this->mb_WakeCond)*/
|
||
}
|
||
this->TMutexCondData::UnLock();
|
||
}/*
|
||
_SY_ void WakeCondBoolSync()*/
|
||
|
||
|
||
bool DoWaitCond() const{return mb_WaitCond;}
|
||
|
||
public:
|
||
};/*
|
||
template< typename TMutexCondData=ZNsMain::ZtCMutexCondData<>
|
||
>
|
||
class ZtCMutexCondVar ///////////////////////////////////////*/
|
||
|
||
|
||
}/*
|
||
namespace ZNsMain */
|
||
|
||
|
||
|
||
/*////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
■ DCLP (Double-Checked Locking Pattern) 을 쓰지 말아야 하는 이유
|
||
|
||
※ http://blog.naver.com/jjoommnn?Redirect=Log&logNo=130036635345
|
||
|
||
※ 위 페이지의 글을 요약하면 다음과 같다.
|
||
|
||
MyClass* GP_MyClassSingle=0;
|
||
|
||
MyClass& GetMyClassSingle()
|
||
{
|
||
static MyLock SO_MyLock;
|
||
|
||
if(GP_MyClassSingle!=0) // -- 1)
|
||
return *GP_MyClassSingle;
|
||
//endif
|
||
|
||
SO_MyLock.Lock()
|
||
{
|
||
if(GP_MyClassSingle!=0) // -- 2)
|
||
return *GP_MyClassSingle;
|
||
//endif
|
||
|
||
GP_MyClassSingle=new MyClass; // -- 3)
|
||
}
|
||
SO_MyLock.UnLock()
|
||
}
|
||
//MyClass& GetMyClassSingle()
|
||
|
||
GetMyClassSingle() 함수를 Thread1 과 Thread2 에서 호출한다고 해보자. 먼저 Thread1
|
||
이 GetMyClassSingle() 에 진입해서 3) 까지 실행했다.
|
||
|
||
※※
|
||
|
||
이 시점에서 GP_MyClassSingle 은 NULL 이 아니고 MyClass 의 생성자도 실행이 완전
|
||
히 끝나지 않았을 수도 있다. 컴파일러의 재배치 때문에 이럴 수 있다. 전역 포인터
|
||
GP_MyClassSingle 을 volatile 로 선언한다고 해서 해결되지 않는다. volatile 는
|
||
해당 변수를 레지스터에서 읽어오지 말라고 지정하는 키워드이지 cache 에서 읽지
|
||
말고 메모리에서 읽어오라고 지정하는 키워드가 아니다.
|
||
|
||
http://msdn.microsoft.com/en-us/library/12a04hfd(VS.80).aspx 를 참고하면 Window
|
||
에서(좀 더 정확히는 MSVC++ 에서) volatile 은 메모리 barrier 의 효과를 가진다.
|
||
|
||
http://minjang.egloos.com/2370662 도 좋은 글이다.
|
||
|
||
위 페이지에서 답변자는 Window 에서 volatile 을 이용한 경우, Double Checked Lock
|
||
도 정확하게 동작한다고 말하고 있다.
|
||
|
||
-- 2009-11-19 14:06:00
|
||
|
||
|
||
## volatile 예제 In http://msdn.microsoft.com/en-us/library/12a04hfd(VS.80).aspx
|
||
|
||
// spin lock 이 되는 것을 눈여겨 보자.
|
||
|
||
// volatile.cpp
|
||
// compile with: /EHsc /O2
|
||
#include <iostream>
|
||
#include <windows.h>
|
||
using namespace std;
|
||
|
||
volatile bool Sentinel = true;
|
||
int CriticalData = 0;
|
||
|
||
unsigned ThreadFunc1( void* pArguments ) {
|
||
while (Sentinel)
|
||
Sleep(0); // volatile spin lock
|
||
|
||
// CriticalData load guaranteed after every load of Sentinel
|
||
cout << "Critical Data = " << CriticalData << endl;
|
||
return 0;
|
||
}
|
||
|
||
unsigned ThreadFunc2( void* pArguments ) {
|
||
Sleep(2000);
|
||
CriticalData++; // guaranteed to occur before write to Sentinel
|
||
Sentinel = false; // exit critical section
|
||
return 0;
|
||
}
|
||
|
||
int main() {
|
||
HANDLE hThread1, hThread2;
|
||
DWORD retCode;
|
||
|
||
hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadFunc1,
|
||
NULL, 0, NULL);
|
||
hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadFunc2,
|
||
NULL, 0, NULL);
|
||
|
||
if (hThread1 == NULL || hThread2 == NULL) {
|
||
cout << "CreateThread failed." << endl;
|
||
return 1;
|
||
}
|
||
|
||
retCode = WaitForSingleObject(hThread1,3000);
|
||
|
||
CloseHandle(hThread1);
|
||
CloseHandle(hThread2);
|
||
|
||
if (retCode == WAIT_OBJECT_0 && CriticalData == 1 )
|
||
cout << "Success" << endl;
|
||
else
|
||
cout << "Failure" << endl;
|
||
}
|
||
|
||
## -- volatile 예제
|
||
|
||
※※
|
||
|
||
이때 Thread2 가 GetMyClassSingle() 을 호출한다. 1) 코드에서 GP_MyClassSingle 은
|
||
NULL 이 아니다. 그래서 MyClass 참조를 return 한다. 그런데 MyClass 의 생성자가 완전
|
||
히 수행되기 전일 수 있다. 즉 중요한 멤버가 초기화되지 않았을 수 있다. 따라서
|
||
Thread2 는 멤버가 완전히 초기화되지 않은 MyClass object 로 작업을 하게 되는 불상사
|
||
가 일어날 수 있다.
|
||
|
||
■ 이 DCL(Double-Checked Locking) 문제를 피하는 몇 가지 방법을 정리하면,
|
||
|
||
1) 생성자와 그 클래스의 정적 object 가 private 에 있고 그 정적 object 에 접근하는
|
||
인터페이스만 제공한다.
|
||
2) 단순무식하게 동기화 영역에서만 수행한다.
|
||
3) (특히 멀티 쓰레딩의 경우) 참조카운팅을 사용한다.
|
||
4) 멤버변수가 없거나 멤버변수가 모두 초기화되었는지 알 수 있다면 DCL 을 사용해도
|
||
된다.
|
||
5) 각 쓰레드마다 공유하는 자원이 어떤 전역 문자열 설 정정보이고, 이 정보가 가끔씩
|
||
만 업데이트 된다면 차라리 각 쓰레드마다 자신의 버퍼에 할당해서 가지고 있는 것
|
||
도 방법이다.
|
||
|
||
■ singleton 의 비싼 동기화를 피하기위해 특별히 천재적인 프로그래머들은 double-checked
|
||
locking 이디엄을 개발했다. 약한 메모리 모델의 분야를 재정의하는 작업이 진행중이다.
|
||
하지만 아무리 새로운 메모리 모델이어도 double-checked locking 은 작동하지 않을 것이
|
||
다. 이 문제에 대한 최상의 솔루션은 동기화를 수락하거나 static field 를 사용하는 것
|
||
이다.
|
||
|
||
[출처] double-checked locking | 작성자 뚜벅제왕
|
||
|
||
http://blog.naver.com/zugm?Redirect=Log&logNo=40003946986
|
||
|
||
Peter Haggar 는 IBM 의 소프트웨어 엔지니어이다. Practical Java Programming Language
|
||
Guide (Addison-Wesley) 의 저자이며 자바 프로그래밍 관련하여 많은 글을 집필했다. 개
|
||
발 툴, 클래스 라이브러리, OS 등 광범위한 분야에 많은 경험을 갖고 있다.
|
||
|
||
□ 위 URL 에서 Peter Haggar 는 java 에서는 out-of-order write 때문에, DCL 이 안전
|
||
하지 않다고 하며(즉 re-ordering 을 방지할 수 없으며), volatile 을 사용한다고 해
|
||
도, volatile 자체는 메모리 베리어의 역할을 한다고 java 사양에 정의하고 있지만
|
||
|
||
많은 JVM 이 순차적 영속성을 정확히 고려한 volatile 을 구현하지 않는다
|
||
|
||
고 하고 있다.
|
||
|
||
□ --
|
||
|
||
■ 특정 쓰레드를 (주로) 특정 프로세서(CPU)에 할당하는 함수
|
||
|
||
□ Window 에서
|
||
|
||
DWORD SetThreadIdealProcessor(
|
||
HANDLE hThread,
|
||
DWORD dwIdealProcessor
|
||
);
|
||
|
||
※ Remarks
|
||
|
||
You can use the GetSystemInfo function to determine the number of processors on the computer.
|
||
You can also use the GetProcessAffinityMask function to check the processors
|
||
on which the thread is allowed to run.
|
||
Note that GetProcessAffinityMask returns a bit mask
|
||
whereas SetThreadIdealProcessor uses an integer value to represent the processor.
|
||
To compile an application that uses this function,
|
||
define the _WIN32_WINNT macro as 0x0400 or later.
|
||
For more information, see Using the SDK Headers.
|
||
|
||
[출처] 특정 코어(CPU) 에 쓰레드 할당 api.. 와우..|작성자 초보가축
|
||
|
||
※ http://blog.naver.com/seoru?Redirect=Log&logNo=40045312217
|
||
|
||
□ Linux 에서
|
||
|
||
int pthread_setaffinity_np(pthread_t th,size_t size,const cpu_set_t *cpuset);
|
||
int pthread_getaffinity_np(pthread_t th,size_t size,cpu_set_t *cpuset);
|
||
int pthread_attr_setaffinity_np(pthread_attr_t *at,size_t size,const cpu_set_t *cpuset);
|
||
int pthread_attr_getaffinity_np(pthread_attr_t *at,size_t size,cpu_set_t *cpuset);
|
||
|
||
|
||
#define _GNU_SOURCE
|
||
#include <sched.h>
|
||
|
||
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<mo_CNetBuffSendList.size();++i)
|
||
{
|
||
// some code
|
||
}
|
||
|
||
와 같이 되어야 한다. 몇 년 동안 저 부분을 왜 못잡았을까? 회고해 보면 저 송수신 버퍼
|
||
관련 코드를 작성했을 때, 단위 테스트를 적절하게 하지 않았었다. 그리고 일정한 크기가
|
||
될 때까지 수신 패킷을 수신 버퍼에 더하는 과정에서도, 더하는 지점을 잘못 계산해서 1460
|
||
번 바이트가 유실되는 문제를 겪어서 몇 달 동안 개고생을 했었는데, 위 for 코드 문제는
|
||
3 년 동안이나 눈치조차 채지 못하고 있었다.
|
||
|
||
단위 테스트를 소홀히 한 것은, 결국 관리와 감독을 잘못한 것이며, 바꾸어 말하면 '지휘'
|
||
를 잘못한 것이다. 좀 다르게 풀어보면 '지휘관'으로써의 '통찰력'과 '애정'이 부족했던
|
||
것이라고 볼 수 있다. '지휘관'으로부터 '애정'을 받지 못한 병사들이 어떻게 '지휘관'을
|
||
믿고 따를 것이며, 위기의 순간에 흔들리지 않을 수 있겠는가.
|
||
|
||
이 버그를 잡기 까지, 그야말로 격전을 치르면서 코드가 말도 못하게 견고해졌다. 이제는
|
||
난공불락의 철옹성이나 다름 없으며, 공격력 또한 막강한 정예 군단으로 탈바꿈해 있다.
|
||
|
||
-- 2010-01-23 16:46:00
|
||
|
||
'정예 군단'? 에라 이 미친 놈아. 넌 태어나지 말았어야 했었어. -- 2013-07-19 13:45:00
|
||
|
||
■ 리눅스에서 최대 thread 제한은
|
||
|
||
cat /proc/sys/kernel/threads-max
|
||
|
||
으로 알 수 있다. -- 2014-08-23 17:38:00
|
||
|
||
■ 리눅스의 파일 lock 방식은 2가지가 존재하고 이에 대한 설명은 다음과 같다.
|
||
|
||
1. Advisory Lock
|
||
|
||
* 커널이 관여하지 않는다.
|
||
* 응용 프로세스 레벨에서 Lock 여부를 확인하여서 처리한다.
|
||
* A 프로세스가 파일을 Lock 하였어도 B 프로세스에서 파일 Lock 을 확인하지 않으면 해
|
||
당 파일에 접근이 가능하다.
|
||
|
||
2. Mandatory Lock
|
||
|
||
* 커널 레벨 Lock 이다.
|
||
* 특정 파일이 Lock 되면 해당 파일에 접근하는 모든 프로세스는 Lock 이 풀릴 때까지 대
|
||
기하게 된다.
|
||
* 파일 시스템 마운트할 때에 -o mand 로 설정해야 한다.
|
||
* 리눅스 기본값은 Mandatory Lock 이 설정되어 있지 않다.
|
||
* Lock 된 파일 접근에서 Dead Lock 현상이 발생할 수 있으므로 주의가 필요하다.
|
||
|
||
////////////////////////////////////////////////////////////////////////////////////*/
|
||
|
||
|
||
#endif //__ZCPPMAIN__PROCESS_H__
|