Inter-thread communication in C++ #7

Posted at 2008. 11. 5. 00:12 // in S/W개발/C++ 이야기 // by 김윤수


ITCThread의 설계상에 심각한 버그가 있었더군요. 다음과 같은 부분이 잘못 됐었습니다.

    void doIt(void (Callee::*mf)(void))
    {
        doIt(CallType(boost::bind(mf, (Callee*)this)));
    }
    void doIt(void (Callee::*mf)(void))
    {
        doIt(CallType(boost::bind(mf, (Callee*)this)));
    }
    template <typename ArgType1, typename ArgType2>
    void doIt(void (Callee::*mf)(ArgType1, ArgType2),
              ArgType1 arg1, ArgType2 arg2)

    {
        doIt(CallType(boost::bind(mf, (Callee*)this, arg1, arg2)));
    }

위 코드를 보면 this를 Callee* 로 무작정 casting을 하고 있는데, 이렇게 할 경우 다음과 같은 방식을 처리하는 것이 불가능하더군요.

struct Base
{
    virtual void print()
    {
        cout << "Base::print()" << endl;
    }
};

struct Derived : public Base
{
    virtual void print()
    {
        cout << "Derived::print()" << endl;
    }
};

struct BaseITCAdaptor : public ITCThread<Base>
{
    BaseITCAdaptor(Base* b, const string& name) :
        ITCThread<Base>(b, name)
    {}

    void print()
    {
        doIt(&Base::print);
    }
};

    Base* b1 = new Base();
    Base* b2 = new Derived();

    BaseITCAdaptor bitcthr(b1, "baseitcthr");
    BaseITCAdaptor ditcthr(b2, "deriveditcthr");
    bitcthr.print();
    ditcthr.print();
    bitcthr.quit(), ditcthr.quit();
    bitcthr.join(), ditcthr.join();
    delete b1;
    delete b2;

즉, ITCThread 별로 polymorphic 하게 member function을 호출하는 것이 불가능했다는 것이죠. 이러한 사용예를 가능하게 하기 위해 ITCThread를 다음과 같이 수정했습니다.

template <typename Callee, typename PolicyType = WaitAndExecute>
class ITCThread
{
public:
    ITCThread(Callee* callee, const std::string name = "itcthr") :
        m_callee(callee), m_thr(), m_policy(new PolicyType),
        m_name(name), m_run(true), m_code(0)
    {
        m_thr = boost::thread(&ITCThread::run, this);
    }
    ......
private:
    ......
    Callee* m_callee;
    ......
};

그리고, doIt member function들은 다음과 같이 수정했습니다. 이전에는 (Callee*)this 와 같이 강제 형변환을 수행했던 부분을 m_callee 멤버 변수를 활용하여 수정했습니다.

    void doIt(void (Callee::*mf)(void))
    {
        assert(m_callee != 0);
        doIt(CallType(boost::bind(mf, m_callee)));
    }
    template <typename ArgType>
    void doIt(void (Callee::*mf)(ArgType), ArgType arg)
    {
        assert(m_callee != 0);
        doIt(CallType(boost::bind(mf, m_callee, arg)));
    }
    template <typename ArgType1, typename ArgType2>
    void doIt(void (Callee::*mf)(ArgType1, ArgType2),
              ArgType1 arg1, ArgType2 arg2)

    {
        assert(m_callee != 0);
        doIt(CallType(boost::bind(mf, m_callee, arg1, arg2)));
    }
    template <typename ArgType1, typename ArgType2, typename ArgType3>
    void doIt(void (Callee::*mf)(ArgType1, ArgType2, ArgType3),
              ArgType1 arg1, ArgType2 arg2, ArgType3 arg3)
    {
        assert(m_callee != 0);
        doIt(CallType(boost::bind(mf, m_callee, arg1, arg2, arg3)));
    }

FunctionITCAdaptor는 다음과 같이 수정했습니다.

template <typename PolicyType = WaitAndExecute>
struct FunctionITCAdaptor : public ITCThread<Nothing, PolicyType>
{
    FunctionITCAdaptor(const std::string& name) :
        ITCThread<Nothing, PolicyType>(0, name) {}
};

    // example #3
    s = "hello";
    Hello* h = new Hello();
    HelloITCAdaptor hello(h, s);
    for (int i = 0; i < 1000; ++i)
    {
        hello.sayHelloTo("Yoonsoo Kim");
    }
    hello.quit();
    hello.join();
    delete h;

    // example #4
    s = "btn_handler";
    ButtonHandler* bh = new ButtonHandler();
    ButtonHandlerITCAdaptor btnHdlr(bh, s);
    btnHdlr.doIt(&ButtonHandler::onClick, 10, 20);
    btnHdlr.quit();
    btnHdlr.join();
    delete bh;

XXXITCAdaptor 형태의 예제는 초기화 인자로 호출대상이 되는 클래스 인스턴스를 넣어줘야 됩니다. ITCThread에서 상속 받아서 wrapping member function을 작성하는 경우에는 다음과 같이 생성자를 작성해 주면 됩니다.

class HelloThread : public ITCThread<HelloThread>
{
public:
    HelloThread(const string& name = "hello") :
        ITCThread<HelloThread>(this, name) {}
    ......
};

class PrintVector : public ITCThread<PrintVector>
{
public:
    PrintVector(const string& name = "prvec") :
        ITCThread<PrintVector>(this, name)
    {}
    ......
};

이상은 설계 및 구현상의 버그를 수정한 것이고, 이 외에 다음과 같은 수정 사항이 있습니다.

  • ITCAdaptor 삭제
  • ITCThread::quit()을 default 인자를 활용하여 하나로 통합
  • ITCThread::doQuit()을 인자가 하나인 것만 유지하고 종료 코드값을 내부적으로 유지
  • ITCThread::join() 이 quit()호출시 지정했던 종료 코드를 리턴하도록 수정
  • ProcessingPolicy 라는 인터페이스 클래스를 삭제하고, WaitAndExecute, ProcessAndExecute와의 상속 관계를 없앰.
  • WaitAndExecute, ProcessAndExecute 의 구현 내용을 itc.h 파일로 모두 옮김
혹시 소스 코드 보시다가 잘못된 부분이나 개선 사항을 제안하고 싶으신 것이 있으면 얘기해 주세요~