Inter-thread communication in C++ #4

Posted at 2008. 10. 27. 23:23 // in S/W개발/C++ 이야기 // by 김윤수


2008/10/26 - [S/W개발/C++ 이야기] - Inter-thread communication in C++ #3

저번글에서 말씀드렸던 대로 다음 사항을 추가해 봤습니다.

1. 꼭 멤버 함수 뿐만이 아닌 이미 정의되어 있는 함수를 특정 thread context에서 호출하는 기능
2. callIt 멤버 함수들을 public 으로 변경하고, 이름을 doIt으로 바꿈

이외에도

3. 파일들을 여러 개로 분리해 봤습니다.

1. 번을 위해서는 다음에 보시는 것처럼 기존에 있던 doIt 멤버 함수 외에 여러 종류의 doIt 템플릿 멤버 함수를 추가했습니다.

/// @file itc.h
#include "itcbase.h"

namespace itc {

template <class ThreadType>
struct ITCThread : private details::ITCThreadBase
{
    ......
    // 인자 없는 일반 함수
    void doIt(void (*func)(void))
    {
        doIt(CallType(func));   
    }

    // 인자가 하나인 일반 함수
    template <typename ArgType>
    void doIt(void (*func)(ArgType), ArgType arg)
    {
        doIt(CallType(boost::bind(func, arg)));
    }

    // 인자가 둘인 일반 함수
    template <typename ArgType1, typename ArgType2>
    void doIt(void (*func)(ArgType1, ArgType2),
              ArgType1 arg1, ArgType2 arg2)

    {
        doIt(CallType(boost::bind(func, arg1, arg2)));
    }

    // 인자가 셋인 일반 함수
    template <typename ArgType1, typename ArgType2, typename ArgType3>
    void doIt(void (*func)(ArgType1, ArgType2, ArgType3),
              ArgType1 arg1, ArgType2 arg2, ArgType3 arg3)
    {
        doIt(CallType(boost::bind(func, arg1, arg2, arg3)));
    }
};

};

2번을 위해서는 다음과 같이 코드를 변경했습니다.

/// @file itcbase.h
// namespace도 정의했습니다.
namespace itc { namespace details {

class ITCThreadBase : public boost::thread {
public:
    void doIt(CallType call);
    ......
};

/// @file itc.h
namespace itc {

template <class ThreadType>
struct ITCThread : private details::ITCThreadBase
{
    ......
    using details::ITCThreadBase::doIt;
    ......
};

};

사용 예는 다음 코드를 참고하시기 바랍니다.

/// @file itc.cpp

#include <iostream>
#include <string>
#include <queue>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include "itc.h"             // ITCThread를 쓰려면 include해야 함

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

// example #1 : new class which inherits from ITCThread
// 이런식으로 ITCThread 에서 상속을 받은 후, ITC방식으로 호출 가능하게
// 만들고 싶은
member function(hello, yahoo)을 직접 정의해 줍니다.
// member function을 정의할 때는
doIt을 사용하시면 됩니다.
class HelloThread : public ITCThread<HelloThread>
{
public:
    HelloThread(const string& name = "hello") :
        ITCThread<HelloThread>(name) {}
   
    void hello(const string& name)
    {
        doIt(&HelloThread::doHello, name);
    }

    void yahoo()
    {
        doIt(&HelloThread::doYahoo);
    }

private:
    void doHello(const string name)
    {
        cout << getName() << ": Hello~ " << name << endl;
    }
   
    void doYahoo()
    {
        cout << getName() << ": Yahoo~~~~~!" << endl;
    }
};

// example #2 : new class which inherits from ITCThread
// example #1과 동일한 패턴입니다
class PrintVector : public ITCThread<PrintVector>
{
public:
    PrintVector(const string& name = "prvec") :
        ITCThread<PrintVector>(name)
        {}
   
    void print(const vector<int>& vi)
    {
        doIt(&PrintVector::doPrint, vi);
    }
   
    void printTwoVectors(const vector<int>& vi,
                         const vector<double>& vd)

    {
        doIt(&PrintVector::doPrintTwoVectors, vi, vd);
    }
   
private:
    void doPrint(const vector<int> vi)
    {
        copy(vi.begin(), vi.end(), ostream_iterator<int>(cout, " "));
        cout << endl;
    }

    void doPrintTwoVectors(const vector<int> vi,
                           const vector<double> vd)

    {
        copy(vi.begin(), vi.end(), ostream_iterator<int>(cout, " "));
        cout << endl;
        copy(vd.begin(), vd.end(),
             ostream_iterator<double>(cout, " "));

        cout << endl;
    }
};

// example #3: legacy class + wrapping member function
// 기존에 이미 있던 클래스(struct Hello)를 ITCThread context안에서
// 수행되게 하고 싶은
경우에는 다음과 같은 패턴을 사용하시면 됩니다.
struct Hello
{
    void sayHelloTo(const string name)
    {
        cout << "Hello, " << name << "!" << endl;
    }
};

struct HelloProxy : private Hello, public ITCThread<Hello>
{
    HelloProxy(const string& name) : ITCThread<Hello>(name) {}

    // Hello::sayHelloTo()에 대한 wrapping member function을
    // doIt을 이용해 작성합니다.

    void sayHelloTo(const string& name)
    {
        doIt(&Hello::sayHelloTo, name);
    }
};

// example #4: legacy class, no wrapping member function
// example #3과 비슷하지만 wrapping member function을 작성하지 않는
// 경우입니다.
이렇게 할 경우, 사용하는 측에서 명시적으로 doIt을
// 불러줘야 합니다.

// main 함수의 예제 코드를 보세요.
struct ButtonHandler
{
    void onClick(int x, int y)
    {
        cout << "button clicked at (" << x << ", " << y << ")" << endl;
    }
};

struct ButtonHandlerITCAdaptor : private ButtonHandler,
                                 public ITCThread<ButtonHandler>
{
    ButtonHandlerITCAdaptor(const string& name):
        ITCThread<ButtonHandler>(name)
        {}

    // no wrapping member function
};

// example #5
// 일반 C함수 형태도 호출할 수 있습니다.
void printHello()
{
    cout << "Hello, world" << endl;
}

void sayHelloTo(const string name)
{
    cout << "Hello, " << name << "!" << endl;
}

void sayHelloToAndGoodByeTo(const string name1, const string name2)
{
    cout << "Hello, " << name1 << "!" << endl;
    cout << "Good-bye, " << name2 << "!" << endl;
}

struct FunctionalThread : public ITCThread<FunctionalThread>
{
    FunctionalThread(const string& name):
        ITCThread<FunctionalThread>(name)
        {}

};

int
main()
{
    // example #1
    HelloThread itc1("H1"), itc2("H2");
   
    for (int i = 0; i < 10; ++i)
    {
        this_thread::sleep(millisec(1000));
        itc1.hello("world!"), itc1.yahoo();
        this_thread::sleep(millisec(50));
        itc2.hello("body!"), itc2.yahoo();
    }
    this_thread::sleep(millisec(1000));
    itc1.quit(1);
    this_thread::sleep(millisec(50));
    itc2.quit(2);
    itc1.join(), itc2.join();

    // example #2
    PrintVector prvec;

    vector<int> vi;
    for (int i = 0; i < 10; ++i)
    {
        vi.push_back(i);
    }
    prvec.print(vi);
    this_thread::sleep(millisec(50));

    vector<double> vd;
    for (int i = 0; i < 10; ++i)
    {
        vd.push_back(i * 10.5);
    }
    prvec.printTwoVectors(vi, vd);

    prvec.quit();
    prvec.join();
   
    // example #3
    HelloProxy hello("hello");
    for (int i = 0; i < 1000; ++i)
    {
        hello.sayHelloTo("Yoonsoo Kim");
    }
    hello.quit();
    hello.join();

    // example #4
    ButtonHandlerITCAdaptor btnHdlr("btn_handler");
    btnHdlr.doIt(&ButtonHandler::onClick, 10, 20);
    btnHdlr.quit();
    btnHdlr.join();
   
    // example #5
    FunctionalThread fthr("fthr");
    fthr.doIt(printHello);
    fthr.doIt(sayHelloTo, string("Yoonsoo Kim"));
    fthr.doIt(sayHelloToAndGoodByeTo,
              string("Yoonsoo Kim"),
              string("Yeongsuk Jeong"));

    fthr.quit();
    fthr.join();

    return 0;
}

이상으로 이해하는데 도움이 되셨길 바랍니다. 앞으로 리턴값이 있는 함수를 지원하는 것도 한 번 해볼까 합니다.