Files
RepoMain/ZCppMain/ZCProcess.H

1775 lines
68 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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__