Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

BoostAsioで可読性を求めるのは間違っているだろうか

0 views

Published on

Boost.Asio を読みやすくする。

Published in: Engineering
  • Hello there! Get Your Professional Job-Winning Resume Here! http://bit.ly/topresum
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

BoostAsioで可読性を求めるのは間違っているだろうか

  1. 1. Boost.Asioに可読性を求めるのは 間違っているだろうか 株式会社ムラサメ研究所 宮竹ゆき
  2. 2. 自己紹介 株式会社ムラサメ研究所(税金対策会社)主神 会社名はノリでつけました。 普通にエンジニアです。ファミリアメンバー(社 畜)募集中! ゲーム系、低レイヤー系、カーネル系 等が得意分野です C++はSTLやTemplateは今まで使ってきませんでし た 完全に食わず嫌いです constexprが読めない 大都会岡山の恥さらしです 三度の飯より TKG(卵かけごはん)が好き
  3. 3. はじめに  boost 1.57.0を前提にすすめます(1.58 でも動作確認はしたが)  Cmake clang++ でビルドしてます  MacとLinuxで確認していますが、マ ルチプラットフォームなはず  C++14歴1ヶ月未満の初心者です  サンプルのソースは動作確認してな いので参考まで
  4. 4. Asioを使う事になった理由  速度が要求されるサーバの案件 元のソースが bind(…); listen(…); accept(…); begin_thread(…); マルチスレッド型では同時接続数を増 やすの難しい! そうだ boost.Asioにしよう!
  5. 5. Boost.Asioとは  Asynchronous I/O。proactorパターン  シングルスレッドでI/Oを非同期で処理する  従来のmultithreadパターンだと、threadのコストが高 く、大量のクライアントを さばけない(C10K問題)  カーネルにread/write命令を送り、完了時にコール バックさせるselect/epoll等のnonblocking I/Oと比較し て、user/kernelのコンテキストスイッチが少なくなる と思われる LinuxではepollでProacotr実装している模様  関数型プログラミング勉強してみたかったので、コー ルバック地獄は本望だ  ラムダたん
  6. 6. 実例 boost::asio::async_read(socket_, boost::asio::buffer(data, data.length()), [this](boost::system::error_code ec, std::size_t ){ if (!ec) { boost::asio::async_write(socket_, boost::asio::buffer(data, data.length()), [this](boost::system::error_code ec, std::size_t ){ if (!ec){ ..... }else{ //writeのエラー処理 } }); } }else{ // readのエラー処理・・ } }); はようコールバックまみれになろうぜ
  7. 7. 可読性を上げる方法  コールバックを自力でなんとかする coroutineを使う  promise/futureを使う promise/futureは難しそうなので 今回はcoroutineを使う事に
  8. 8. coroutineとは  Fiber、micro thread等色々あるが、 軽量スレッドの事  疑似並列化を行う事が可能(協調的、 ノンプリエンプティブ)  シングルスレッドなので同期問題が 起こりにくい  Threadと違い、自分でCPUを開放す る(協調的)
  9. 9. stackless coroutine  簡単そうだったのでこちらから  asio::coroutineを継承する  適用範囲は #include <boost/asio/yield.hpp> ~ #include <boost/asio/unyield.hpp> で囲む  coroutine関数は operator()に operator()以外でも出来そうだけど調べてない  coroutine部分は reenter()で囲む  yield fork等をキーワードのように使え る
  10. 10. header  asio::coroutineを継承する事 class server : boost::asio::coroutine {  コールバック先の関数オブジェクト void operator()(ほげ)  fork時にコンストラクタが呼ばれないので、 shared_ptr等を使うと初期化しやすい(指摘あれ ばよろしくお願いします) std::shared_ptr<tcp::acceptor> acceptor_; std::shared_ptr<tcp::socket> socket_; std::shared_ptr<std::array<char, 1024> > buffer_;
  11. 11. operator()、reenter  コルーチンが再開される場所  CPUが割り当てられた際に、登録した 関数オブジェクトハンドラが呼ばれる  reenter内の前回中断した場所から再開 される void server::operator()(boost::system::error_co de ec, std::size_t length) { if (!ec) { reenter(this){
  12. 12. fork  forkを使うと新たな疑似コンテキストを作成で きる  is_parentで親子が判定出来る  新たなコンテキストは、個別のメンバを割り当 てられる(が コンストラクタはforkでは呼ばれ ない) do { socket_.reset(new tcp::socket(acceptor_->get_io_service())); yield acceptor_->async_accept(*socket_, *this); fork server(*this)(); } while (is_parent());
  13. 13. yield  非同期処理をpostし、CPUを開放する  非同期処理完了時に、指定した関数オ ブジェクトがコールバックされる yield socket_>async_read_some(boost::asio::buff er(*buffer_), *this); yield boost::asio::async_write(*socket_, boost::asio::buffer(*buffer_), *this);
  14. 14. 例 // coroutineを使わない例 asio::async_read(socket_, asio::buffer(buffer_), [this](boost::system::error_code ec, std::size_t ){ asio::async_write(socket_, asio::buffer(buffer_), [this](boost::system::error_code ec, std::size_t ){ socket_.shutdown(tcp::socket::shutdown_both, ec); }); } }); // stackless_coroutineを使った例 reenter(this){ do{ yield acceptor_->async_accept(socket_,*this); fork server(*this)(); }while(is_parent()); yield socket_.async_read_some(asio::buffer(buffer_), *this); yield socket.async_write_some(asio::buffer(buffer_), *this); socket_.shutdown(tcp::socket::shutdown_both, ec); }; 可読性あがりましたよね?
  15. 15. メリット  非常に簡単お手軽に書く事が出来る  かなり軽そう(switch caseで実装さ れてる)  コールバック地獄がなくなり、一見 直列処理になり、ソースの可読性も 非常に良くなった  ヘッダの追加のみで ライブラリの追 加が不要
  16. 16. 問題点  switch caseで実装されているため、 ローカル変数の扱いが制限される ローカル変数の扱い方が難しくなるた め、使用を辞めた
  17. 17. 参考  stackless coroutine Overview http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/overvie w/core/coroutine.html  AsioSample HTTP server4 http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/exampl es/cpp03_examples.html
  18. 18. stackful coroutine  依存ライブラリ ./b2 --with-system --with-thread --with- date_time --with-regex --with-serialization  asio::spawn()に、コルーチンを行いたい関数 (オブジェクト)を指定しyield_contextを取得 する  Stacklessと違い、スタックコンテキストを個 別に作成するので、ローカル変数も可能  Asynchronous命令のコールバックに yield_contextを指定する 意外に簡単そう
  19. 19. spawn spawnにより yield_contextを作成する 複数の接続を受ける場合は connection毎に spawnする必要がある boost::asio::spawn(io_service, [&] (boost::asio::yield_context yield) { … for (;;) { tcp::socket _socket(io_service); acceptor.async_accept(_socket, yield); // C++14の汎用ラムダキャプチャ欲しい・・ asio::spawn(strand_, [_socket](asio::yield_context yield_child) { tcp::socket socket(std::move(_socket)); ….. yield_child を使う! }); } }
  20. 20. yield  非同期関数にyield contextを渡す事で、コルーチンに 出来る acceptor.async_accept(socket_, yield); // tcp::socketのメンバ関数でもOK socket_.async_read_some(asio::buffer(buffer_), yield); // asioのフリー関数でもOK asio::async_write(socket_, asio::buffer(buffer),yield);
  21. 21. 例外処理  yieldコンテキストを渡す際、通常は例外がthrowされ るが、operator[]を使えばエラーコード方式に出来る // 例外throw方式 socket_.async_read_some(boost::asio::buffer(buffer_), yield); // エラーコード方式 boost::system::error_code; ec;socket_.async_read_some(boost::asio::buffer(buffer_), yield[ec]); 老害は速度の速いエラーコード方式が好き
  22. 22. 例 // coroutineを使わない例 asio::async_read(socket_, asio::buffer(buffer_), [this](boost::system::error_code ec, std::size_t ){ asio::async_write(socket_, asio::buffer(buffer_), [this](boost::system::error_code ec, std::size_t ){ socket_.shutdown(tcp::socket::shutdown_both, ec); }); } }); // coroutineを使った例 boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield){ boost::system::error_code ec; socket_.async_read_some(asio::buffer(buffer_), yield[ec]); socket.async_write_some(asio::buffer(buffer_), yield[ec]); socket_.shutdown(tcp::socket::shutdown_both, ec); }); 可読性あがりましたよね?
  23. 23. 参考  stackful coroutine Overview http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/overvie w/core/spawn.html  Asio sample spawn http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/exampl e/cpp11/spawn/echo_server.cpp
  24. 24. 有限状態マシン(finite state machine)  通信に限らずStateパターンを使う事は 多いが、大抵はこの部分の可読性が悪 くなる  一般的にはenumでモードを設定しint型 と相互変換して使ったり、switch caseあ るいは関数ポインタ配列で実装したり  State変更関数作ったり・・  大抵は Stateがネストしたり、State変更 条件が色々あったり・・・ それらが大幅に簡略化されます
  25. 25. Boost.MSM  有限状態マシン(finite state machine)  Boost.Statechartより速い(RTTIや virtualを使用してないっぽい)  Boost::MPLを使う  StateとEventを作成する  transition_tableを作成し、Eventにより Stateを変更できる  ガード、アクションを指定出来る
  26. 26. Boost.MSM // 状態作成 struct start : public boost::msm::front::state<>{}; struct end : public boost::msm::front::state<>{}; // イベント作成 struct event_goal{}; // イベントテーブル作成 struct state : public msm::front::state_machene_def<state> { struct table : mpl::vector< _row<start, event_goal, end> // goalイベントが来たら endへ遷移 > …. } state st; st.process_event(goal()); // goalイベント呼ばれたので endへ遷 移
  27. 27. Boost.MSM  Transition_tableに上限がある mpl::vectorを使っているので BOOST_MPL_LIMIT_VECTOR_SIZE を指定する  コンパイル時間がかかる pImplイデオム使おう  コンパイル時にステートが決まってる必要がある 動的にステートを作らなければOK  遷移の拒否(Guard)が出来る UML2準拠らしい  遷移時のアクションを指定できる Stateでon_enter()、on_exit()をオーバーライド出来る が、それ以外に transition_tableに関数オブジェクトを登 録できる  複雑な状態遷移がある場合にはとても便利
  28. 28. boost.Asio Tips
  29. 29. shared_ptrキャプチャ class session{ void do_read(){ async_read( socket_,buffer,[this](){this->hoge;}); } } 非同期なのでハンドラ実行時にthisは開放されているかもしれない ハンドラを実行しようと心の中で思ったならッ!その時スデにオブ ジェクトは終わってるんだッ! class session::enable_shared_from_this<session>{ void do_read(){ auto self(shared_from_this()); async_read( socket_,buffer,[this, self](){this->hoge;}); } } 自分のshared_ptrをキャプチャし、ハンドラ終了までオブジェクトを 終了させない
  30. 30. timeout処理  coroutine時のtimeoutに一晩うなされた結果、下記のように行いま した asio::deadline_timer_; timer_.expires_from_now(boost::posix_time::seconds(10)); timer_.async_wait( [&socket_](const boost::system::error_code &ec) { if (ec == boost::asio::error::operation_aborted) { cout << “cancel” << endl ; } else { socket_.cancel(); cout << “timeout” << endl ; } }); socket_.async_read_some(boost::asio::buffer(buffer_), yield[ec]); timer_.cancel(); これで正しいのかわかりませんが、現状動いています
  31. 31. 総括  マルチスレッド型は大量クライアントをさばけない(C10K)  マルチスレッド型だとデータの同期処理にコストがかかる ので、非同期 I/Oを使うべき  非同期I/Oを普通に書くとコールバック地獄に陥る  coroutineを使うと、直列っぽく記述出来るので可読性が上 がる  stackless_coroutineはとてもコストが低そうだが、switchを 使ったマクロなため、ローカル変数等の制限が発生する  stackful_coroutineは制限もなくとても便利である!  ステートマシンは Boost.Statechartあるいは Boost.MSMを使 えばよい  C++のジェネリックラムダが来ると更に快適になりそうだ

×