読者です 読者をやめる 読者になる 読者になる

Boost.Asio postとdispatchの違い

C++

Boost.Asioのio_serviceには、post()とdispatch()というほぼ同じことをする関数が用意されています。
以下のブログでわかりやすく解説されていたので、それを見ていくことにします。


To post or to dispatch? - This Thread


まず、io_serviceを複数スレッドで動作させます。
それと、最初にメインスレッドのIDを出力しておきます。

std::cout << boost::this_thread::get_id() << std::endl;

asio::io_service io_service;
asio::io_service::work work(io_service);

boost::thread_group group;
const int count = 3;
for (int i = 0; i < count; ++i) {
    group.create_thread(boost::bind(&asio::io_service::run, &io_service));
}

複数スレッドで実行されているio_serviceに同じ関数をpost()します。
ここのpost()は、post()/dispatch()の違いには直接関係しません(間接的には関係ある)。

while (startAFunction()) {
    io_service.post(boost::bind(fA, boost::ref(io_service)));
}

io_serviceにpost()された関数の中では、post()とdispatch()を交互に呼び出すようにしています。
post()とdispatch()はそれぞれ同じ関数を呼び出します。ここでも現在のスレッドのIDを出力します。

void fA(asio::io_service& io_service)
{
    static int selector = 0;

    ++selector;
    if (selector % 2 == 0) {
        print(boost::this_thread::get_id(), " post");
        io_service.post(fB);
    }
    else {
        print(boost::this_thread::get_id(), " dispatch");
        io_service.dispatch(fB);
    }
}

post()とdispatch()によって呼ばれる関数fBは、単に現在のスレッドのIDを出力しているだけです。

void fB()
{
    print(boost::this_thread::get_id(), " call fB");
}

これを実行すると以下のような結果になります(読みやすいように一部編集しています):

00154EE0

00154FB8 dispatch
00154FB8 call fB

00154FB8 post
00154F58 call fB

00154F58 dispatch
00154F58 call fB

dispatch()によって呼ばれた関数は、dispatch()されたのと同じスレッドで呼ばれていて、
post()の場合は異なるスレッドで呼ばれていることがわかります。


dispatch()は、呼び出し元の関数が非同期に実行されている場合に非同期ではなく直接その関数を呼び出し、そうでなければpost()と同様にキューに登録して非同期に実行する、という挙動をします。
そのため、シングルスレッドでio_serviceを動かしている場合にはpost()とdispatch()に違いはなく、マルチスレッドで動かしたときのみdispatch()が特定の状況下において効率的になり得ます。

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

namespace asio = boost::asio;

boost::mutex mutex;
void print(boost::thread::id id, const std::string& s)
{
    boost::mutex::scoped_lock lock(mutex);
    std::cout << id << s << std::endl;
}

void fB()
{
    print(boost::this_thread::get_id(), " call fB");
}

void fA(asio::io_service& io_service)
{
    static int selector = 0;

    ++selector;
    if (selector % 2 == 0) {
        print(boost::this_thread::get_id(), " post");
        io_service.post(fB);
    }
    else {
        print(boost::this_thread::get_id(), " dispatch");
        io_service.dispatch(fB);
    }
}

bool startAFunction()
{
    print(boost::this_thread::get_id(), " Enter a non-empty string to run A function");

    std::string input;
    getline(std::cin, input);
    return input.length() == 0 ? false : true;
}

int main()
{
    std::cout << boost::this_thread::get_id() << std::endl;

    asio::io_service io_service;
    asio::io_service::work work(io_service);

    boost::thread_group group;
    const int count = 3;
    for (int i = 0; i < count; ++i) {
        group.create_thread(boost::bind(&asio::io_service::run, &io_service));
    }

    while (startAFunction()) {
        io_service.post(boost::bind(fA, boost::ref(io_service)));
    }

    std::cout << "stop io_service" << std::endl;
    io_service.stop();
    group.join_all();

    std::cout << "end application" << std::endl;
}