Inter-thread communication in C++ #1

Posted at 2008.10.22 10:08 // in S/W개발/C++ 이야기 // by 김윤수


아래 코드는 독립된 쓰레드로 동작하는 Class의 member function을 그 쓰레드 context에서 실행시키는 예제 코드. 이걸 좀더 일반적으로 작성할 순 없을까요? 예를 들어, member function 인자가 몇 개가 있더라도 호출할 수 있도록 한다던지... ITCThread에서 상속받는 클래스에서 공개 인터페이스를 쉽게 작성할 수 있도록 해준다던지...(지금은 memfun()를 public으로 하고 doMemfunc()라는 걸 private으로 선언한 다음에 memfun()를 구현해 줘야 한다.)

좋은 아이디어 있으신 독자분께서는 댓글 부탁드려용~

참고로 말씀드리면 thread, mutex, unique_lock, condition_variable, bind, mem_fn 등은 모두 boost에 있는 것들을 사용한 것입니다.

#include <iostream>

#include <string>
#include <functional>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/signal.hpp>

using namespace std;
using namespace boost;
using namespace boost::posix_time;

template <class ThreadType>
class ITCThread : public thread
{
public:
    typedef void (ThreadType::*MemFunType)();

    ITCThread(const string& name = "thr"):
        thread(bind(mem_fn(&ITCThread::run), this)),
        m_name(name),
        m_mutex(), m_cond(),
        m_req(false), m_fn(&ITCThread::noop), m_run(true) {}

    const string& getName()
    {
        return m_name;
    }

    void quit()
    {
        doIt(&ITCThread::doQuit);
    }

protected:
    void doIt(MemFunType mf)
    {
        unique_lock<mutex> lock(m_mutex);
        cout << m_name << ": calling..." << endl;
        m_fn = mem_fun(mf);
        m_req = true;
        m_cond.notify_one();
    }

private:
    void run()
    {
        cout << m_name << ": starting..." << endl;
        while (m_run)
        {
            unique_lock<mutex> lock(m_mutex);
            while (!m_req)
            {
                cout << m_name << ": waiting a call..." << endl;
                m_cond.wait(lock);
            }
            cout << m_name << ": I've got a call" << endl;
            m_fn((ThreadType*)(this));
            m_req = false;
        }
        cout << m_name << ": bye bye" << endl;
    }

    void doQuit()
    {
        cout << m_name << ": exiting..." << endl;
        m_run = 0;
    }

    void noop()
    {
        cout << m_name << ": noop() called" << endl;
    }

    string m_name;
    mutex m_mutex;
    condition_variable m_cond;
    bool m_req;
    mem_fun_t<void,ThreadType>  m_fn;
    bool m_run;
};

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

    void hello()
    {
        doIt(&HelloThread::doHello);
    }

private:
    void doHello()
    {
        cout << getName() << ": Hello~ body!" << endl;
    }

};

int
main()
{
    HelloThread itc1("H1"), itc2("H2");

    for (int i = 0; i < 10; ++i)
    {
        this_thread::sleep(millisec(1000));
        itc1.hello();
        itc2.hello();
    }
    this_thread::sleep(millisec(1000));
    itc1.quit();
    itc2.quit();

    itc1.join();
    itc2.join();

    return 0;
}

신고
,
  1. 셈말짓기

    2008.10.22 19:33 신고 [수정/삭제] [답글]

    저도 그런걸로 고민한적이 있긴한데..
    막상 떠오르진 않더라구요..
    여튼 좋은 아이디어를 알려드리는 건 아니지만....
    코드 중에..
    생성자 정의부분에서 :옆에 멤버 변수 초기하는 부분에
    this 포인터를 사용하는건 조금 위험한거 아닌가요?
    물론 위에 코드에선 문제 없지만... ~_~;
    ...
    ITCThread(const string& name = "thr"):
    thread(bind(mem_fn(&ITCThread::run), this)),
    ...

    • 김윤수

      2008.10.23 03:56 신고 [수정/삭제]

      일반적으로는 위험하죠. 이 예에서는 별로 문제가 되진 않는 것 같습니다. 그렇지만 말씀하신대로 constructor에서 this 쓰는 건 아무래도 조심해야죠. 저도 최근에 this 를 constructor에서 잘못 썼다가 호되게 당했답니다.

  2. 최익필

    2008.10.26 01:02 신고 [수정/삭제] [답글]

    저만 이런 생각을 한게 아니군요; 저 같은 경우 부스트를 사용하지 않아서, 위의 코드를 잘 모르겠습니다. 대신 조금 간단하게, 함수 객체를 쓰레드로 쓰는 방법인데,

    /* CThreadWrapper : 쓰레드 기본 포장자
    */
    template <class _Tx>
    class CThreadWrapper : private BeNotCopied
    {
    public:
    static unsigned int __stdcall WraptThreadFunction(void * tpArg )
    {
    _Tx NowThread;
    return NowThread.Thread( tpArg );
    }

    public:
    CThreadWrapper() { }
    ~CThreadWrapper() { }
    };

    그리고 _beginthreadex() 의 인자를 이 객체의 WraptThreadFunction를 붙여주고, _Tx 에 Thread() 함수를 정의 하면 됩니다.

    필요한 경우 기본 포장자를 확장하여, 멤버를 갖는 구조로 갈 수 있습니다. 이때는 static 함수에 멤버를 넣는 트릭을 사용 해야 합니다.

    • 김윤수

      2008.10.27 21:01 신고 [수정/삭제]

      예~ 말씀하신 경우도 자주 필요하죠. 재밌는 패턴이네요. 한가지 아쉬운 점이 있다면 tpArg 를 void* 가 아닌 type-safe 한 방법을 찾으면 좋을 것 같습니다.

      주로 window 쪽에서 programming 하시나 봅니다. 저는 window 쪽은 잘 몰라서... _beginthreadex()는 window 함수인가요?

  3. kalstein

    2008.10.26 17:29 신고 [수정/삭제] [답글]

    ACE framework에서 사용되는 ACE_Task를 좀 더 쉽게 사용 할 수 있는 방안...으로도 확장할 수 있겠군요. (proactor 패턴이던가..그랬을꺼에요) 거기서는 ACE_Message 라는걸 이용해서 데이터만 넣어주거든요. 그럼 해당되는 데이터를 processing 하는 구조였지요. processing 함수를 여러개 thread로 살려서 process효율을 높이는 구조랄까요.

    활용되면 프로그래밍이 좀 쉬워질법한데...최종본을 보니 사용법이 상당히 복잡하네요. 저도 한번 생각해봐야할듯...

    • 김윤수

      2008.10.27 20:59 신고 [수정/삭제]

      ACE_Task 가 정확히 어떤 구조로 작동하는지는 모르겠지만... 저는 기존에 많이 쓰던 방식인 Message Queue 방식을 좀 더 type-safe 하게 할 수 있는 방법을 찾다보니 위 글과 같은 방식을 고안하게 됐습니다. Message Queue 를 쓰는 방식들은 보통 보면 void * 를 쓰게 되고 다시 type casting 을 하게 되는 식으로 프로그래밍을 하기 때문에 compile time 시에 type 관련 에러들이 잡히질 않게 됩니다. 반면에 제가 고안한 방식은 type-safe 를 보장할 수 있게 됩니다.

      앞으로 좀 더 발전시켜볼 생각입니다. ^^

댓글을 남겨주세요.

티스토리 툴바