fc2ブログ

[C++] スレッドからのコールバック

2013-04-11 | 13:40

自分が生成したスレッドからのコールバックを受け取る方法

Listener方式

コールバック関数が定義されているListenerクラスを作成し、コールバックを受け取る側のクラスでListenerを継承する。

#include <iostream>  
#include <functional>  
#include <boost/function.hpp>
#include <boost/thread.hpp>

class Listener{
    public:
        virtual void callback()=0;
};

class Child{  // (Listenerについて知っていれば)Parentのことは直接知らなくても良い
    public:
    void set_listener(Listener* p){
        listener_ = p;
    }

    void bar(){
        std::cout << "Child::bar(" << boost::this_thread::get_id() << ")" << std::endl;

        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        // Listenerのcallback()を呼び出す
        listener_->callback();
    }

    private:
    Listener* listener_;
};

// Listenerを継承(implement)する
class Parent : public Listener{
    public:
    void foo(){
        // Listenerとして自分を登録しておいて
        child_.set_listener(this);
        // bar()を別スレッドで呼び出す --- (1)
        boost::thread t([this]()->void{
                this->child_.bar();
            });
    }

    void callback(){
        std::cout << "Parent::callback(" << boost::this_thread::get_id() << ")" << std::endl;
    }

private:
    Child child_;
};

int main(){
    std::cout << "main start(" << boost::this_thread::get_id() << ")" << std::endl;
    Parent parent;
    parent.foo();
    std::cout << "Parent::foo() called" << std::endl;

    boost::this_thread::sleep(boost::posix_time::milliseconds(3000));

    std::cout << "exit." << std::endl;

    return 0;
}

スレッドにコールバック関数を登録しておく方式

この方法だと、コールバックを受け取る側はListenerを継承する必要はない。

#include <iostream>
#include <boost/function.hpp>
#include <boost/thread.hpp>

class Child{  
    public:
    // コールバック関数を登録
    void set_callback(boost::function<void (void)>& f){
        callback_ = f;
    }

    void bar(){
        std::cout << "Child::bar(" << boost::this_thread::get_id() << ")" << std::endl;

        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        // callbackを呼び出す
        callback_();
    }

    private:
    boost::function<void (void)> callback_;
};


class Parent {
    public:
    void foo(){
        // callback()をchild_のコールバック関数として登録
        boost::function<void (void)> func = boost::bind(&Parent::callback, this);
        child_.set_callback(func);

        // bar()を別スレッドで呼び出す --- (1)
        std::cout << "I will call Child::bar()(" << boost::this_thread::get_id() << ")" << std::endl;
        boost::thread t([this]()->void{
                this->child_.bar();
                });
    }

    void callback(){
        std::cout << "Parent::callback(" << boost::this_thread::get_id() << ")" << std::endl;
    }

private:
    Child child_;
};

int main(){
    std::cout << "main start(" << boost::this_thread::get_id() << ")" << std::endl;
    Parent parent;
    parent.foo();
    std::cout << "Parent::foo() called" << std::endl;

    boost::this_thread::sleep(boost::posix_time::milliseconds(3000));

    std::cout << "exit." << std::endl;

    return 0;
}

boost::signals2を使う

#include <iostream>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/thread.hpp>

class Child{  
    public:
    // コールバック関数を登録
    void set_callback(boost::function<void (void)>& f){
        sig_.connect(f);
    }

    void bar(){
        std::cout << "Child::bar(" << boost::this_thread::get_id() << ")" << std::endl;

        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        // signalを送る
        sig_();
    }

    private:

    boost::signals2::signal<void (void)> sig_;
};

class Parent {
    public:
    void foo(){
        // callback()をchild_のコールバック関数として登録
        boost::function<void (void)> func = boost::bind(&Parent::callback, this);
        child_.set_callback(func);

        // bar()を別スレッドで呼び出す --- (1)
        std::cout << "I will call Child::bar()(" << boost::this_thread::get_id() << ")" << std::endl;
        boost::thread t([this](){
                this->child_.bar();
                });
    }

    void callback(){
        std::cout << "Parent::callback(" << boost::this_thread::get_id() << ")" << std::endl;
    }

private:
    Child child_;
};

int main(){
    std::cout << "main start(" << boost::this_thread::get_id() << ")" << std::endl;
    Parent parent;
    parent.foo();
    std::cout << "Parent::foo() called" << std::endl;

    boost::this_thread::sleep(boost::posix_time::milliseconds(3000));

    std::cout << "exit." << std::endl;

    return 0;
}

上の例ではコールバック関数は1つだけだが、signals2には複数のコールバック関数を登録できる。

コールバック関数を、呼び出し元のスレッドではなく、メインのスレッド側で実行したい。

上記の全サンプルでは、コールバック関数は(1)で生成したスレッド(以下、thread-1)上で実行される(実行時に括弧内で表示されているidを参照)。thread-1を生成した側のメインのスレッド(thread-M)とは別スレッドなので、コールバック関数が呼ばれた時に行う処理(以下、処理A)によっては、排他制御を考慮する必要がある。また、処理Aに時間がかかる場合は、その間thread-1側の処理がブロックされることになる。そこで、コールバック関数が呼ばれた後に処理Aをthread-M側で処理する方法を考える。

boost.asioを使った実装

コールバック関数内でio_serviceに処理Aをポストしておき、コールバック関数はすぐにリターンする。io_serviceにポストされた処理Aはthread-M側で実行される。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/function.hpp>
#include <boost/thread.hpp>

class Child{  
    public:
    // コールバック関数を登録
    void set_callback(boost::function<void (void)>& f){
        callback_ = f;
    }

    void bar(){
        std::cout << "Child::bar(" << boost::this_thread::get_id() << ")" << std::endl;

        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        // callbackを呼び出す
        callback_();
    }

    private:
    boost::function<void (void)> callback_;
};


class Parent {
    public:
    void foo(){

        // io_serviceのハンドラが空になっても終了しないようにする
        boost::asio::io_service::work w(io_);

        // callback()をchild_のコールバック関数として登録
        boost::function<void (void)> func = boost::bind(&Parent::callback, this);
        child_.set_callback(func);

        // bar()を別スレッドで呼び出す
        std::cout << "I will call Child::bar()(" << boost::this_thread::get_id() << ")" << std::endl;
        boost::thread t([this]()->void{
                this->child_.bar();
                });

        io_.run();
    }

    void callback(){
        std::cout << "Parent::callback(" << boost::this_thread::get_id() << ")" << std::endl;
        // ハンドラをポストする
        io_.post([](){
                 // thread-M上で実行される処理
                boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
                std::cout << "posted function(" << boost::this_thread::get_id() << ")" << std::endl;
        });
        std::cout << "posted(" << boost::this_thread::get_id() << ")" << std::endl;
    }

private:
    Child child_;
    boost::asio::io_service io_;
};

int main(){
    std::cout << "main start(" << boost::this_thread::get_id() << ")" << std::endl;
    Parent parent;
    parent.foo();

    return 0;
}
スポンサーサイト



Comment

Post a comment

Secret