Edited at

Boost.Asioのio_serviceで非同期実行


概要

C++のよく知られたライブラリとしてBoostがあります。このBoostのネットワークライブラリとしてAsioがあります。

Boost.AsioにはOSとの仲立ちをしてくれる機能としてboost::asio::io_serviceがあります。

ここでは、これを利用した非同期実行の方法を書いていきます。


io_serviceにできること

シングルスレッドでの非同期実行。Node.jsみたいな感じで単一スレッド上でキューに入っている処理を非同期に実行してくれます。

マルチスレッドの非同期実行。シングルスレッドでもそこそこに面倒な非同期処理をマルチスレッドでも可能にしてくれます。


とりあえず実行してみる

シングルスレッドでのソースと実行結果を載せます。

// clang++ -std=c++11 -lboost_system test1.cpp

#include <iostream>

#include <boost/asio/io_service.hpp>

namespace {
// とりあえずの処理1
void func1() {
std::cout<<"func1"<<std::endl;
}
// とりあえずの処理2
void func2() {
std::cout<<"func2"<<std::endl;
}
}

int main() {
boost::asio::io_service ios;

// io_serviceの中にあるキューに追加する
for(std::size_t i=0; i<2; ++i) {
ios.post(func1);
ios.post(func2);
}

// キューがなくなるまで実行を続ける
ios.run();
}

実行結果

func1

func2
func1
func2

これだけでは何も有り難みがありませんが、通信などいつ処理が起きるかわからないような用途ではちょうど良い仕組みだと思います。


マルチスレッドで実行

シングルスレッドでもマルチスレッドでもある程度の処理は同じです。(内部の処理は全然違うかもしれませんが)

シングルスレッドとの一番の違いはrunを複数のスレッドで呼ぶことです。

io_serviceではrunが呼ばれたスレッドで処理を実行していきます。


とりあえず書いてみる

// clang++ -std=c++11 -lboost_system -lpthread test.cpp

#include <thread>
#include <iostream>

#include <boost/asio/io_service.hpp>

namespace {
// とりあえずの処理1 改(1秒停止)
void func1() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"func1"<<std::endl;
}
// とりあえずの処理2 改(3秒停止)
void func2() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout<<"func2"<<std::endl;
}
}

int main() {
boost::asio::io_service ios;

// io_serviceの中にあるキューに追加する
for(std::size_t i=0; i<2; ++i) {
ios.post(func1);
ios.post(func2);
}

// キューがなくなるまで実行を続ける
// スレッド
std::thread t([&ios]{ios.run();});
// メインスレッド
ios.run();

t.join();
}

実行結果

func1

func1
func2
func2

このように複数のスレッドで実行した場合、処理が終わったスレッドから順次処理を消化してくれます。


postとdispatch

io_serviceにはpostdispatchメンバ関数があります。この関数の違いは処理が扱われ方にあります。

postは普通にキューに処理を追加し順番が回ってきたら自動的に実行されます。

dispatchはキューとは関係なく処理を一時停止し、渡された処理をその場で実行します。


余談

C++ではC++11からstd::threadが標準になりましたが、複数のスレッドをまとめて立てるときはコンテナに入れる方法しかありません。また、コンテナに収まっていると言ってもすべて別々のスレッドクラスであるため一つ一つの項目に対してjoin()を呼ばないといけないためめんどくさいです。

そこで、あえて標準のスレッドを使わず、Boostのスレッドを使う手があります。Boostのスレッドにはthread_groupというクラスがあり、このクラスは属するスレッドをまとめてjoinできるjoin_allという関数があります。


参考


テストプログラムの環境

OS : Linux Mint 18(x64)

コンパイラ : clang++ 3.8.0

Boost : 1.58.0