yutopp's blog

サンドバッグになりたい

いそのーBoostビルドしようぜー

(2011/12/26現在のNDKはr7boostは1.48.0です。また開発環境はWindows機、AndroidAPIレベルは8としています。)

C++を使ってでのAndroidのアプリの開発はGoogleの配布しているNative Development Kitを用いることで難なく行うことができますが、含まれているgccのバージョンが4.4.3であるため中々ヒャッハーすることができませんでした。
という訳でCrystaX .NETにて配布されているカスタマイズされたNDKを使い、ついでにboostもビルドして使ってみたのでメモっておきます。NDKについても初心者なので間違ったこと書いている可能性大です><

はじめに

NDKでもBoost使いたいってメモなので、CygwinAndroid SDKEclipseなどの環境は既に整えられているものとします。(ちなみに私はEclipse 日本語化 | MergeDoc ProjectのUltimate入れました。)
NDKについてもちょっとググっておいて下さい。

材料はこちら
Android NDK-r7(少々)
Improved Android NDK-r7
Boost 1.48.0

私はAndroid NDKをCドライブ直下に置いたので、それぞれのパスはC:/android-ndk-r7C:/android-ndk-r7-crystax-3となっています。
CygwinC:/cygwinに置いているので、boostのパスはC:/cygwin/usr/include/boost_1_48_0となっています。自分の環境に合わせて適宜読み替えて下さい。

Boostビルドしたいだけの人は最初は読み飛ばして下さい。
また、操作はCygwinbashで行なってます。

単純にBoost!

まずは単純にgcc4.6.3とboostライブラリ(ヘッダオンリー)を使ってみます。
プロジェクトの作り方はAndroid JNIプロジェクトをゼロから作る(UsefullCode.net)、C++での開発方法はAndroid NDKでC++を利用する(UsefullCode.net)を参考に作成してください。
jniディレクトリは
f:id:yutopp:20111226204027p:plain
こんな感じになっていれば平気です。kotatu.cppは読み替えて下さい。こたつ!こたつ!
C++のコードをEclipseを使って書く予定が無いのであれば、Android NDKを使う(アプリの高速化) « Tech Boosterが参考になります。
Eclipseで丸々開発を行うのであれば、そらとぶくじら。 EclipseからAndroid NDK/JNIをスマートにビルドする方法。を参考に設定しておくと便利です。
もちろん ANDROID_NDK_ROOT はC:/android-ndk-r7-crystax-3に変えて下さい。
またコードの補完のために、プロジェクトのプロパティを開き[C/C++ 一般]→[パス及びシンボル]のインクルード タブにあるGNU C++に以下のように追加しておくと良いです(お好みで)。
f:id:yutopp:20111226203343p:plain
さて、Application.mkはこんな感じになります。
Application.mk

APP_STL		:= gnustl_static
APP_CPPFLAGS	+= -frtti
APP_CPPFLAGS	+= -fexceptions
APP_CPPFLAGS	+= -std=gnu++0x

APP_TOOLCHAIN_VERSION	:= 4.6.3
APP_USE_CPP0X		:= true

現時点でのAndroid.mkはこんなところです。
Android.mk

LOCAL_PATH := $(call my-dir)

# アプリ
include $(CLEAR_VARS)
LOCAL_MODULE    		:= kotatu
LOCAL_LDLIBS			:= -llog
LOCAL_C_INCLUDES		+= /usr/include/boost_1_48_0
LOCAL_SRC_FILES			:= kotatu.cpp
include $(BUILD_SHARED_LIBRARY)

これである程度ヒャッハー出来るようになります。
ですがthreadやfilesystemなどのリンクが必要なライブラリは使うことができません。という訳でビルドしましょう!

Android用にBoostをビルドする

試行錯誤中なのでアドバイス下さい!
まずはbootstrapを実行してb2.exeを作っておきます。
次にuser_config.jamを編集します。C:/cygwin/usr/include/boost_1_48_0/tools/build/v2 にあります。
これが中々よく分からない・・・。Tips & Tricks: Building Boost with NDK R5 - Code Xperimentsを参考に書きました。
とりあえず以下のものをuser_config.jamに付け足して下さい。
user_config.jam

# アンドロイド用
modules.poke : NO_BZIP2 : 1 ;
ANDROID_NDK = C:/android-ndk-r7-crystax-3 ;
ANDROID_NDK_OFFICIAL = C:/android-ndk-r7 ;

using gcc : android : 
$(ANDROID_NDK)/toolchains/arm-linux-androideabi-4.6.3/prebuilt/windows/bin/arm-linux-androideabi-g++ : 
<cxxflags>-I$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/include/4.6.3 
<cxxflags>-I$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/libs/armeabi/4.6.3/include 
<cxxflags>-I$(ANDROID_NDK)/platforms/android-8/arch-arm/usr/include 
<cxxflags>-I$(ANDROID_NDK_OFFICIAL)/platforms/android-8/arch-arm/usr/include 
<cxxflags>-D__ARM_ARCH_5__ 
<cxxflags>-D__ARM_ARCH_5T__ 
<cxxflags>-D__ARM_ARCH_5E__ 
<cxxflags>-D__ARM_ARCH_5TE__ 
<cxxflags>-DNDEBUG 
<cxxflags>-D_REENTRANT 
<cxxflags>-D_GLIBCXX__PTHREADS 
<cxxflags>-D_LITTLE_ENDIAN 
<cxxflags>-DBOOST_NO_FENV_H 
#<cxxflags>-DBOOST_FILESYSTEM_VERSION=2
<cxxflags>-std=gnu++0x 
<cxxflags>-g
<cxxflags>-mthumb 
<cxxflags>-msoft-float 
<cxxflags>-mtune=xscale 
<cxxflags>-march=armv5te 
<cxxflags>-Wa,--noexecstack 
<cxxflags>-Wall 
<cxxflags>-pthread 
<cxxflags>-frtti 
<cxxflags>-fexceptions 
<cxxflags>-fpic 
#<cxxflags>-fomit-frame-pointer 
<cxxflags>-fno-strict-aliasing 
<cxxflags>-ffunction-sections 
<cxxflags>-funwind-tables 
<cxxflags>-fstack-protector 
<architecture>arm 
<find-static-library>c 
<xdll-path>$(ANDROID_NDK)/platforms/android-8/arch-arm/usr/lib/ 
<library-path>$(ANDROID_NDK)/platforms/android-8/arch-arm/usr/lib/ 
<archiver>$(ANDROID_NDK)/toolchains/arm-linux-androideabi-4.6.3/prebuilt/windows/bin/arm-linux-androideabi-ar 
<ranlib>$(ANDROID_NDK)/toolchains/arm-linux-androideabi-4.6.3/prebuilt/windows/bin/arm-linux-androideabi-ranlib 
;

(アドバイスを頂いたので書き換えました。ありがとうございます。ただいくつか上手くいかなかったのでそこだけそのままです。)
今までの工程ではまったく公式のNDKを使っていなかったのですが、ここでインクルードパスに追加しています。
wchar.hがカスタマイズ版に入っていないので苦肉の策・・・。きもちわるいでござる。
BOOST_FILESYSTEM_VERSION=2も付け足したほうが良いかもしれません。v3が使えないので。

またいくつかのboostのコードに変更を加えなければいけません・・・
Wt - Installing Wt on Android - Redmineを参考にします。

boost/asio/detail/fenced_block.hpp

28行目と57行目

#elif defined(__GNUC__) && defined(__arm__)

#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__)

に変更。

37行目と66行目

  && !defined(__ICC) && !defined(__ECC) && !defined(__PATHSCALE__)

  && !defined(__ICC) && !defined(__ECC) && !defined(__PATHSCALE__) && !defined(__ANDROID__)

に変更。

boost/asio/detail/socket_types.hpp

126行目あたり

 const int max_addr_v4_str_len = INET_ADDRSTRLEN;

#ifdef INET_ADDRSTRLEN
 const int max_addr_v4_str_len = INET_ADDRSTRLEN;
#else
 const int max_addr_v4_str_len = 16;
#endif

に変更。

boost/asio/ip/impl/address_v6.ipp

13行目あたりの空白に

#ifndef IN6_IS_ADDR_MULTICAST 
#define IN6_IS_ADDR_MULTICAST(a) (((__const uint8_t *) (a))[0] == 0xff)
#endif

#ifndef IN6_IS_ADDR_MC_NODELOCAL
#define IN6_IS_ADDR_MC_NODELOCAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                             \
         && ((((__const uint8_t *) (a))[1] & 0xf) == 0x1))
#endif

#ifndef IN6_IS_ADDR_MC_GLOBAL
#define IN6_IS_ADDR_MC_GLOBAL(a) \
        (IN6_IS_ADDR_MULTICAST(a)                                             \
         && ((((__const uint8_t *) (a))[1] & 0xf) == 0xe))
#endif

を追加。

libs/filesystem/v2/src/v2_operations.cpp

61行目

#   if !defined(__APPLE__) && !defined(__OpenBSD__)

#   if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__ANDROID__)

に変更。

66行目付近

#ifdef __OpenBSD__
#     include <sys/param.h>
#endif

#ifdef __OpenBSD__
#     include <sys/param.h>
#elif __ANDROID__
#     include <sys/vfs.h>
#endif

に変更。

1272行目付近

          long tmp = ::pathconf( "/", _PC_NAME_MAX );

#ifdef __ANDROID__
          long tmp = 4096;
#if 0
          {
            int fd = open( "/", O_RDONLY );
            if (fd >= 0) {
              tmp = ::fpathconf( fd, _PC_NAME_MAX );
              close(fd);
            }
          }
#endif
#else
          long tmp = ::pathconf( "/", _PC_NAME_MAX );
#endif

に変更。

libs/filesystem/v3/src/operations.cpp

83行目

#   if !defined(__APPLE__) && !defined(__OpenBSD__)

#   if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__ANDROID__)

に変更。

88行目付近

#     ifdef __OpenBSD__
#     include <sys/param.h>
#     endif

#     ifdef __OpenBSD__
#     include <sys/param.h>
#     elif __ANDROID__
#     include <sys/vfs.h>
#     endif

に変更。

220行目付近

 #   define BOOST_RESIZE_FILE(P,SZ)(::truncate(P, SZ)== 0)

#ifndef __ANDROID__
#   define BOOST_RESIZE_FILE(P,SZ)(::truncate(P, SZ)== 0)
#else
int BOOST_RESIZE_FILE(const char *path, off_t size)
{
   int retval = -1;
   int fd = open(path, O_WRONLY);
   if (fd != -1)
      retval = ftruncate(fd, size);
   close(fd);
   return retval;
}
#endif

に変更。

・・・と、大体こんな感じにすると良いようです。

ではビルドしてみましょう。
カレントディレクトリをC:/cygwin/usr/include/boost_1_48_0に移し

cd /usr/include/boost_1_48_0

以下のように打ち込んで下さい。

./b2 --without-python --without-serialization toolset=gcc-android link=static runtime-link=static target-os=linux --stagedir=android

(2012/1/22 訂正)
鬱陶しい警告が出ますが、最終的にC:/cygwin/usr/include/boost_1_48_0/android/lib にライブラリが生成されているはずです。

ではリンクするためにAndroid.mkを書き換えます。
Android.mk

# パスをメモっておく
PROJECT_LOCAL_PATH := $(call my-dir)

# ビルド済み
LOCAL_PATH	:= /usr/include/boost_1_48_0/android/lib
# thread
include $(CLEAR_VARS)
LOCAL_MODULE	:= libboost_thread
LOCAL_SRC_FILES	:= libboost_thread.a
include $(PREBUILT_STATIC_LIBRARY)

# system
include $(CLEAR_VARS)
LOCAL_MODULE	:= libboost_system
LOCAL_SRC_FILES	:= libboost_system.a
include $(PREBUILT_STATIC_LIBRARY)

# filesystem
include $(CLEAR_VARS)
LOCAL_MODULE	:= libboost_filesystem
LOCAL_SRC_FILES	:= libboost_filesystem.a
include $(PREBUILT_STATIC_LIBRARY)

# アプリ
LOCAL_PATH := $(PROJECT_LOCAL_PATH)
include $(CLEAR_VARS)
LOCAL_MODULE    	:= kotatu
LOCAL_LDLIBS		:= -llog
LOCAL_C_INCLUDES	+= /usr/include/boost_1_48_0
LOCAL_STATIC_LIBRARIES	:= libboost_thread
LOCAL_STATIC_LIBRARIES	+= libboost_system
LOCAL_STATIC_LIBRARIES	+= libboost_filesystem
LOCAL_SRC_FILES := kotatu.cpp
include $(BUILD_SHARED_LIBRARY)

ひとまずthreadとsystemとfilesystemをリンクしてみた図。他にリンクを追加する場合も同じように1つずつ書いていきます。
これでうまくいくはず!

まとめ

もっと簡単な方法無いんですか!

ひとまず自分の環境ではこれでうまく出来ました。
ですが不安たっぷりなのでなにかありましたらぜひ教えて下さいorz

ドゥラァァイ

どうしてもやりたかったんです。

アサヒィ
スゥパァ

struct dry
{
	typedef char char_type;
	static const int max_length = 5;

	static const char_type* increment_pointer()
	{
		return "ドゥラ";
	}

	static const char_type* decrement_pointer()
	{
		return "ドゥル";
	}

	static const char_type* repeat()
	{
		return "イ";
	}

	static const char_type* loop()
	{
		return "!";
	}

	static const char_type* increment_value()
	{
		return "ァ";
	}

	static const char_type* decrement_value()
	{
		return "-";
	}

	static const char_type* input()
	{
		return "アイ";
	}

	static const char_type* output()
	{
		return "スゥパァ";
	}
};

int main() {
	//☆.:*:・' .:*:・'゜☆' .:*:・'゜☆' .:*:・'゜☆' .:*:
	//        ス ー パ ー ド ラ イ
	//゜☆' .:*:・'゜☆' .:*:・'゜☆' .:*:・'゜☆' .:*:・'゜
	const std::string s =
		"ドゥラァァァァァァァァァイドゥルァァァァァァァァドゥラ-!ドゥル" \
		"アサヒィスゥパァドゥラァァァァァァァイドゥルァァァァドゥラ-!" \
		"ドゥルァスゥパァァァァァァァァスゥパァスゥパァァァァスゥパァイ-!" \
		"ドゥラァァァァァァァァイドゥルァァァァドゥラ-!ドゥルスゥパァアサヒィ" \
		"ドゥラァァァァァァァァァァァイドゥルァァァァァドゥラ-!アサヒィアサヒィ" \
		"ドゥルアサヒィスゥパァドゥラァァァァァァァァイドゥルァァァドゥラ-!" \
		"ドゥルスゥパァァァァスゥパァ------スゥパァアサヒィ--------" \
		"スゥパァイ-!ドゥラァァァァァァァァイドゥルァァァァドゥラ-!" \
		"ドゥルァスゥパァイ-!ァァァァァァァァァァスゥパァアサヒィ";

	const bool b = brainfuck::parse<dry>( s.cbegin(), s.cend() );
	if ( !b )
		std::cout << " : error..." << std::endl;
}

出力

Hello World!

Boost.Asioまとめ(1)::io_service

Boost Advent Calndar 2011に恐縮ですが参加させて頂きました。15日目です。
最初はBoost.Asioについてまとめるぞーと意気込んでいたものの変に長くなってしまったのでBoost.Asioの中のio_serviceに絞ったためこんなタイトルに成り申した。

Boost.Asioとは

主にネットワークのI/Oのような時間のかかってしまう処理を非同期的かつ簡潔に扱えるようにした便利なライブラリです。
ネットワークを中心に、シリアルポート、タイマー、シグナルのハンドリングなども扱えます。

と、いうわけでio_serviceです。

Windows環境にてVC++10、Boost1.48.0を用いています。

io_service

全てはこのクラスに始まり、このクラスに終わります。
各OSの提供するI/O制御への橋渡しをしてくれるもので、Asioの提供するIOサービス(deadline_timerやip::tcp::socketなど)はこのクラスを必要とします。
ドキュメントによるとProactorという役割を担うものだそうで。
そしてこのクラスへの操作はスレッドセーフです。素敵ですね。
というわけで単体での一番単純なコードです。

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

int main() {
	boost::asio::io_service io;	//(1)

	io.post( [](){ std::cout << "post" << std::endl; } );		//(2)
	io.dispatch( []{ std::cout << "dispatch" << std::endl; } );	//(3)

	io.run();	//(4)
}

まず(1)でio_serviceを作成し、(2)と(3)でキューにハンドラを追加します。そして(4)で呼び出しを行います。
io_serviceのrun関数はこの時点で実行可能なハンドラを呼び出してくれるので、上のコードでは"post"、"dispatch"と順に出力されます。
postとdispatchの違いは後述します。
ちなみにハンドラを追加する際にコピーが行われますが、その動作を変更したい場合は各関数をオーバーロードすることで解決できます。もちろん呼び出しの動作も変更できます。

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

class hoge {
public:
	void operator()() const
	{
		std::cout << "hoge :: operator()!" << std::endl;
	}
};

inline void* asio_handler_allocate( std::size_t size, hoge* )
{
	std::cout << "custom allocator!" << std::endl;
	return ::operator new( size );
}

void asio_handler_deallocate( void* pointer, std::size_t size, hoge* )
{
	::operator delete( pointer );
	std::cout << "custom deallocator!" << std::endl;
}

template<class F>
void asio_handler_invoke( F f, hoge* )
{
	std::cout << "custom invocation!" << std::endl;
	f();
}

int main() {
	boost::asio::io_service io;
	const hoge h;

	io.post( h );
	io.run();
}

・・・シングルスレッドなのでいまいちありがたみを感じられませんね。
という訳でboost::threadを用いて非同期に動かしてみましょう。本領発揮です。奇蹟のカーニバルの開幕ですね。

マルチスレッドで

少し長くなってしまいましたが書き足しました。ついでに意図的に不具合を2つ含めてみました(それ以外のバグは知りません)。

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

//数えるだけ
class counter
{
public:
	counter()
		: i_( 0 )
	{}

	void operator()()
	{
		std::cout << "count: " << i_ << std::endl;
		++i_;
	}

private:
	int i_;
};

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

	//
	boost::asio::io_service io;

	//postで文字を出力させる(1)
	for( int i=0; i<5; ++i )
		io.post( []{ std::cout << boost::this_thread::get_id() << " : post." << std::endl; } );

	//スレッドを4つ作り、runを走らせる(2)
	boost::thread_group tg;
	for( int i=0; i<4; ++i )
		tg.create_thread( boost::bind( &boost::asio::io_service::run, &io ) );

	//1秒待つ(3)
	boost::this_thread::sleep( boost::posix_time::seconds( 1 ) );

	//数えてみたり(4)
	counter c;
	for( int i=0; i<5; ++i )
		io.post( std::ref( c ) );

	//文字を出力してみたり(5)
	for( int i=0; i<5; ++i )
		io.post(
			[&]{ io.dispatch(
				[]{ std::cout << boost::this_thread::get_id() << " : dispatch." << std::endl; }
				);
			} );

	//終わるのを待つ(6)
	tg.join_all();

	std::cout << "finished!" << std::endl;
}

(1)は先ほどと同様、(2)でスレッドを作成して、io_serviceのrun()メンバ関数を動かしています。
さて、(3)でメインスレッドを1秒スリープさせていますが、コレのせいでおそらく(4)(5)辺りが残念なことになります。というか動かないはずです。
原因は、メインスレッドが待機している間に他スレッドでrun()がキューにある実行可能なハンドラを呼び出し尽くして終了してしまっているためです。
そのため、この時点でio_serviceのstopped()メンバ関数はtrueを返します。
この問題はio_service::workというクラスを使うことで解決できます。

io_service::work

io_service::workはio_serviceに「お前にはまだ仕事があるぞ」と吹き込む感じの役割を果たしますので、workが1つでも存在する場合はrun()メンバ関数は働き続けることになります。
(ちなみにrun()はブロッキングを伴いキューに溜まっている物全てを、run_one()はキューに溜まっているものを1つだけ実行することを試み、完了できたハンドラの個数を返します。poll()、poll_one()はブロッキングを伴わないという違いだけでそれ以外は同様の挙動をします。
また、これらの関数はハンドラを実行する前に仕事が溜まっているかチェックし、無い場合はstop()を呼び出しています。
io_service::workは仕事が溜まっているように見せかけるため、各関数の呼び出し時にこのチェックを通りぬけ、ブロッキングを行うrun()とrun_one()は内部でstopフラグが立つまで無限ループ、ブロッキングを行わないpoll()とpoll_one()はガン無視で動作を終了します。なので、poll()またはpoll_one()を用いている場合、workは意味が無いです。)
ということでmain関数を書き足しました。

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

	//
	boost::asio::io_service io;

	/// これ! ///
	boost::asio::io_service::work w( io );
	//////////////

	//postで文字を出力させる(1)
	for( int i=0; i<5; ++i )
		io.post( []{ std::cout << boost::this_thread::get_id() << " : post." << std::endl; } );

	//スレッドを4つ作り、runを走らせる(2)
	boost::thread_group tg;
	for( int i=0; i<4; ++i )
		tg.create_thread( boost::bind( &boost::asio::io_service::run, &io ) );

	//1秒待つ(3)
	boost::this_thread::sleep( boost::posix_time::seconds( 1 ) );

	//数えてみたり(4)
	counter c;
	for( int i=0; i<5; ++i )
		io.post( std::ref( c ) );

	//postとdispatch(5)
	for( int i=0; i<5; ++i )
		io.post(
			[&]{ io.dispatch(
				[]{ std::cout << boost::this_thread::get_id() << " : dispatch." << std::endl; }
				);
			} );

	//終わるのを待つ(6)
	tg.join_all();

	std::cout << "finished!" << std::endl;
}

これで(4)(5)もうまいこと実行されるようになりました。
また、run()が終了しなくなったために(6)で止まるようになります。終了させたい場合はio_serviceのstop()メンバ関数を呼び出すか、io_service::workのオブジェクトを破棄します。
io_service::workはデストラクト時に他に仕事がない場合はio_serviceのstop()メンバ関数を呼び出してくれるので、スマートポインタで保持しておくのも手です。

// #include <boost/make_shared.hpp> が必要!
auto w( boost::make_shared<boost::asio::io_service::work>( io ) );
/*...*/
w.reset();	//じゃあの

run()の実行が1度でも終了しているのならば、次にrun()を呼び出す際はその呼び出しより先にreset()メンバ関数を呼んでおく必要があるということにも注意して下さい。

さて、不具合は残り1つとなりました。(4)だけですね。
複数のスレッドで実行されるので勿論のこと競合してめちゃくちゃになってしまっているはずです。
そんなときこんなとき、役に立つのがio_service::strandというクラスです。ロック操作なんてわざわざ書く必要はありません。

io_service::strand

io_service::strandのpost()またはdispatch()メンバ関数でハンドラを追加するか、wrap()メンバ関数でハンドラをラップして先ほどのようにio_serviceにハンドラを追加する事によって、そのio_service::strandのオブジェクトごとにハンドラをシングルスレッド時と同様に動作させることができます。
ちなみにwrap()はdispatch()を包んだファンクタを返すだけのものです。
・・・またmain関数を書き足し。

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

	//
	boost::asio::io_service io;
	boost::asio::io_service::work w( io );

	//postで文字を出力させる(1)
	for( int i=0; i<5; ++i )
		io.post( []{ std::cout << boost::this_thread::get_id() << " : post." << std::endl; } );

	//スレッドを4つ作り、runを走らせる(2)
	boost::thread_group tg;
	for( int i=0; i<4; ++i )
		tg.create_thread( boost::bind( &boost::asio::io_service::run, &io ) );

	//1秒待つ(3)
	boost::this_thread::sleep( boost::posix_time::seconds( 1 ) );

	/// これ! ///
	boost::asio::io_service::strand st( io );
	//////////////

	//数えてみたり(4)
	counter c;
	for( int i=0; i<5; ++i )
		io.post( st.wrap( std::ref( c ) ) );	////ここ!

	//postとdispatch(5)
	for( int i=0; i<5; ++i )
		io.post(
			[&]{ io.dispatch(
				[]{ std::cout << boost::this_thread::get_id() << " : dispatch." << std::endl;
				} );
			} );

	//終わるのを待つ(6)
	tg.join_all();

	std::cout << "finished!" << std::endl;
}

これで満足に動くようになりました。このドツボにハマってた3年前の自分に読ませたい。

post()とdispatch()

さて、後回しにしていたpost()とdispatch()の違いです。
post()は呼び出された際に単純にキューにハンドラを追加しますが、dispatch()は呼び出された際に呼び出したスレッドでrun()やrun_one()、poll()、poll_one()が動いているならばキューにハンドラを追加すること無くロックを掛け即座にハンドラの呼び出しを行います。
ですので、post()の中でdispatch()を呼び出すようなハンドラを追加するとき(上のコードの(5)の所)は、同期・非同期関係なくdispatch()内のハンドラが即時呼び出されます。
以下のコードで確かめる事ができます。

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

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

	//
	boost::asio::io_service io;

	//
	boost::thread_group tg;

	//postとpost
	std::cout << "- post -> post ------- " << std::endl;
	for( int i=0; i<5; ++i )
		io.post(
			[&,i]{
				const int id = i;
				std::cout << boost::this_thread::get_id() << " : outer(" << id << ")" << std::endl;
				io.post( [id]{ std::cout << boost::this_thread::get_id() << " : inner(" << id << ")" << std::endl;
				} );
			} );
	//
	for( int i=0; i<4; ++i )
		tg.create_thread( boost::bind( &boost::asio::io_service::run, &io ) );
	tg.join_all();

	//postとdispatch
	std::cout << "- post -> dispatch ------- " << std::endl;
	for( int i=0; i<5; ++i )
		io.post(
			[&,i]{
				const int id = i;
				std::cout << boost::this_thread::get_id() << " : outer(" << id << ")" << std::endl;
				io.dispatch( [id]{ std::cout << boost::this_thread::get_id() << " : inner(" << id << ")" << std::endl;
				} );
			} );
	//
	for( int i=0; i<4; ++i )
		tg.create_thread( boost::bind( &boost::asio::io_service::run, &io ) );
	tg.join_all();
}

出力

main thread : 00254EF0
- post -> post -------
00254F98 : outer(0)
00255470 : outer(2)
002551E0 : outer(3)
00255008 : outer(1)
002551E0 : inner(0)
00254F98 : outer(4)
00254F98 : inner(3)
00255008 : inner(4)
00254F98 : inner(1)
00255470 : inner(2)
- post -> dispatch -------
00255170 : outer(0)
00255008 : outer(1)
002550C0 : outer(2)
00255170 : inner(0)
00255008 : inner(1)
002550C0 : inner(2)
00255170 : outer(3)
00255008 : outer(4)
00255170 : inner(3)
00255008 : inner(4)

上手い事いけばこのように整って表示され、どのスレッドでどの番号順にハンドラが呼ばれたか確認することができます。
また、

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING

とAsioを関連のファイルをインクルードする前に定義することによって、動作を追跡出来るようになるので便利です。

最後に

なんだか誰もが知ってるような事ばかりになってしまいました。すみません;
これだけでは物足りないのでタイマーやソケットに関しては別にまとめます!
AsioのネットワークとBoost.Spirit、Serializationなどの組み合わせは最強だと思います。
今回参加させていただいてAsioのコードを読む機会ができ、ためになりましたっ。OSごとの実装の分け方も詳しく調べたいところです。しかしWindowsのAPIはパワフルですね。


16日目は @izmktr さんです。よろしくお願いします。
io.post( []{ read( @izmktr ); } );