いかにしておっぱい画像をダウンロードするか~2012 Dart編
(こんなこと書いてブログのカテゴリ変わったりしないよね?ね?)
元凶
いかにしておっぱい画像をダウンロードするか〜2012 - ゆーすけべー日記いかにしておっぱい画像をダウンロードするか〜2012 Haskell編 - 厨二病患者のプログラミング入門
D言語でいかにしておっぱい画像をダウンロードするか〜2012 — Gist
いかにしておっぱい画像をダウンロードするか〜2012 をC#で書きました - モデラート - C#とゲーム開発と雑記
そんなわけで
最近流行り(ステマ)のDartで書けないかなーと試してみました!背徳感がすごいですね!所々エラー処理は省きました。
(DartEditor build5759、Server applicationで動かせます。dataフォルダの作成と、Bing APIのAppIdの設定を忘れずに~)
(追記)
これもっとDartらしいコードにして欲しいですね > yutopp.hateblo.jp/entry/2012/03/…
— Mr. Fiberさん (@repeatedly) 3月 23, 2012
こうですか、分かりません><
#import("dart:io"); #import("dart:uri"); #import("dart:utf",prefix:"utf"); #import("dart:json"); class Url { static String encodeUtf8(final String src) => _encode(src, (s)=>utf.encodeUtf8(s)); static String _encode(final String rawSrc, List f(final String)) => new String.fromCharCodes((final List src, List dst) { src.forEach( (c) => dst.addAll( ((c >= 65/*'A'*/ && c <= 90/*'Z'*/) || (c >= 97/*'a'*/ && c <= 122/*'z'*/) || (c >= 48/*'0'*/ && c <= 57/*'9'*/) || c == 45/*'-'*/ || c == 95/*'_'*/ || c == 46/*'.'*/ || c == 126/*'~'*/) ? [c] : [37/*%*/, c.toRadixString(16).charCodes()[0], c.toRadixString(16).charCodes()[1]] ) ); return dst; }(f(rawSrc), new List()) ); } void main() { final String baseDir = "data"; final String AppId = "~ここにAppIdを入力~"; final int perDownloadNum = 10; final int maxDownloadNum = 50; final String word = "おっぱい"; int downloadedCount = 0; for (int i=0; i<maxDownloadNum/perDownloadNum; ++i) { final String queryString = (Map<String, String> q) { StringBuffer sb = new StringBuffer(); q.forEach((k,v) => sb.add("${k}=${v}&")); return sb.toString().substring(0, sb.length - 1); }({ "AppId" : AppId, "Version" : "2.2", "Markert" : "ja-JP", "Sources" : "Image", "Image.Count" : "${Math.min(maxDownloadNum-i*perDownloadNum, perDownloadNum)}", "Image.Offset" : "${i*perDownloadNum}", "Adult" : "off", "Query" : Url.encodeUtf8( word ) }); final client = new HttpClient().getUrl(new Uri(scheme: "http", domain: "api.bing.net", path: "/json.aspx", query: queryString)); client.onResponse = (HttpClientResponse res) { StringBuffer resStringBuf = new StringBuffer(); res.inputStream.onData = () => resStringBuf.add(new String.fromCharCodes(res.inputStream.read())); res.inputStream.onClosed = () => JSON.parse(resStringBuf.toString())["SearchResponse"]["Image"]["Results"].forEach((v) => ((Uri path) => new HttpClient().getUrl(path).onResponse = (HttpClientResponse r) { try { final ext = const RegExp(@"\w+$").firstMatch(r.headers["content-type"])[0]; if (ext == "jpeg" || ext == "png" || ext == "gif") { final String filename = const RegExp(@".+/(.+?)\..+$").firstMatch(path.toString())[1]; final File file = new File("$baseDir/$filename.$ext"); final fileStream = file.openOutputStream(); r.inputStream.pipe(fileStream); r.inputStream.onClosed = () => file.create(() => fileStream.close()); print("${++downloadedCount}. ダウンロード! : $baseDir/$filename.$ext"); } } catch(NullPointerException e) { print("error."); } } )(new Uri.fromString(v["MediaUrl"])) ); }; } }
いい感じですね!さぁ、Dart使いましょう!!
(昔の)
#import("dart:io"); #import("dart:uri"); #import("dart:utf"); #import("dart:json"); class Url { static String encode( final String src ) { List ls = <int>[]; for( final int c in encodeUtf8( src ) ) { if ( ( c >= 65/*'A'*/ && c <= 90/*'Z'*/ ) || ( c >= 97/*'a'*/ && c <= 122/*'z'*/ ) || ( c >= 48/*'0'*/ && c <= 57/*'9'*/ ) || c == 45/*'-'*/ || c == 95/*'_'*/ || c == 46/*'.'*/ || c == 126/*'~'*/ ) { ls.add( c ); } else { ls.add( 37/*%*/ ); ls.addAll( c.toRadixString(16).charCodes() ); } } return new String.fromCharCodes( ls ); } } void main() { final String baseDir = "data"; final int perDownloadNum = 10; final int maxDownloadNum = 50; final String word = "おっぱい"; int downloadedCount = 0; for ( int i=0; i<maxDownloadNum/perDownloadNum; ++i ) { final Map<String, String> query = { "AppId" : "~ここにAppIdを入力~", "Version" : "2.2", "Markert" : "ja-JP", "Sources" : "Image", "Image.Count" : "${Math.min(maxDownloadNum-i*perDownloadNum, perDownloadNum)}", "Image.Offset" : "${i*perDownloadNum}", "Adult" : "off", "Query" : Url.encode( word ) }; StringBuffer sb = new StringBuffer(); query.forEach( (k,v) => sb.add("${k}=${v}&") ); final String queryString = sb.toString().substring( 0, sb.length - 1 ); final String uri = "http://api.bing.net/json.aspx?${queryString}"; final client = new HttpClient().getUrl( new Uri.fromString( uri ) ); client.onResponse = ( HttpClientResponse res ) { StringBuffer resStringBuf = new StringBuffer(); res.inputStream.onData = () { resStringBuf.add( new String.fromCharCodes( res.inputStream.read() ) ); }; res.inputStream.onClosed = () { final resJson = JSON.parse( resStringBuf.toString() ); for( final Map v in resJson["SearchResponse"]["Image"]["Results"] ) { final Uri imageUri = new Uri.fromString( v["MediaUrl"] ); final picClient = new HttpClient().getUrl( imageUri ); picClient.onResponse = ( HttpClientResponse r ) { try { ++downloadedCount; final String ext = r.headers["content-type"].replaceAll( const RegExp(@"(\w+)/"), ""); if ( ext == "jpeg" || ext == "png" || ext == "gif" ) { final String filename = imageUri.path.replaceAll( const RegExp(@"(\w+/)"), "" ).replaceAll( const RegExp(@"/"), "" ).replaceAll( const RegExp(@"(\.\w+)"), "" ); print( "$downloadedCount : ダウンロード! : ${baseDir}/${filename}.${ext}" ); final File file = new File( "${baseDir}/${filename}.${ext}" ); final fileO = file.openOutputStream(); r.inputStream.pipe( fileO ); r.inputStream.onClosed = () => file.create( () => fileO.close() ); } } catch( NullPointerException e ) { print( "error." ); } }; } }; }; } }
Dartのstandalone VMを落としてくる方法
windows 7でのメモ。
まずはgclientを使えるようにする。
Install the depot_tools - The Chromium Projects
1. 一番下のInstructions、1.Non-cygwinのリンクのzipを落とし展開。
2. cmd.exeから、depot_toolsに含まれているgclientを一度実行。
3. depot_toolsへのパスを通しておく。
後は、 http://code.google.com/p/dart/wiki/GettingTheSource のGetting the standalone VMの通りにgclientを実行するだけ。
/runtime 下にVS2008用のプロジェクトファイルが出来ている。・・・はず。
型パラメータのインスタンスの生成
Dart 0.07 では型パラメータからインスタンスを生成することは出来ません。(型変数に対してnew・constは使うことが出来ない。)
class hoge<T> { T create() => new T(); // new T()は出来ない。 } class foo { foo() { print("foo!!"); } } main() { final a = new hoge<foo>(); a.create(); }
その対処策としてこのようなものが紹介されていました。
class hoge<T> { hoge( this._creator ); T create() => _creator(); final _creator; } class foo { foo() { print("foo!!"); } } main() { final a = new hoge<foo>( () => new foo() ); a.create(); }
https://groups.google.com/a/dartlang.org/group/misc/browse_thread/thread/1c9d23fcc8480be2#
いそのーBoostビルドしようぜー
(2011/12/26現在のNDKはr7、boostは1.48.0です。また開発環境はWindows機、AndroidAPIレベルは8としています。)
C++を使ってでのAndroidのアプリの開発はGoogleの配布しているNative Development Kitを用いることで難なく行うことができますが、含まれているgccのバージョンが4.4.3であるため中々ヒャッハーすることができませんでした。
という訳でCrystaX .NETにて配布されているカスタマイズされたNDKを使い、ついでにboostもビルドして使ってみたのでメモっておきます。NDKについても初心者なので間違ったこと書いている可能性大です><
はじめに
NDKでもBoost使いたいってメモなので、CygwinやAndroid SDK、Eclipseなどの環境は既に整えられているものとします。(ちなみに私はEclipse 日本語化 | MergeDoc ProjectのUltimate入れました。)
NDKについてもちょっとググっておいて下さい。
材料はこちら
Android NDK-r7(少々)
Improved Android NDK-r7
Boost 1.48.0
私はAndroid NDKをCドライブ直下に置いたので、それぞれのパスはC:/android-ndk-r7、C:/android-ndk-r7-crystax-3となっています。
CygwinはC:/cygwinに置いているので、boostのパスはC:/cygwin/usr/include/boost_1_48_0となっています。自分の環境に合わせて適宜読み替えて下さい。
単純にBoost!
まずは単純にgcc4.6.3とboostライブラリ(ヘッダオンリー)を使ってみます。
プロジェクトの作り方はAndroid JNIプロジェクトをゼロから作る(UsefullCode.net)、C++での開発方法はAndroid NDKでC++を利用する(UsefullCode.net)を参考に作成してください。
jniディレクトリは
こんな感じになっていれば平気です。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++に以下のように追加しておくと良いです(お好みで)。
さて、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を関連のファイルをインクルードする前に定義することによって、動作を追跡出来るようになるので便利です。
はてなブログはじめました。
うほほーいうほほーい