Upload
freedom404
View
1.555
Download
0
Embed Size (px)
Citation preview
並行と並列の違い
並行 (concurrent):複数の処理単位が論理的に独立して動作すること並列 (parallel):実際に複数の処理単位が同時に動作している様子
ほとんどの場合混同されてるし、大方通じるので問題な… …いのでは ?
プロセスとスレッド
どちらも少なくとも並行を実現最近のマルチコア環境ならコアの数だけプロセスもスレッドも並列に走るようにOSがしてくれる
プロセスは独立したプロセス空間を持つが、スレッドはスレッド固有のリソース以外はプロセス内で共有スケジューリングはOSが担当プラットフォーム依存のAPIを使用しないといけないプロセス: fork, CreateProcess ...
スレッド: POSIX Thread, CreateThread ...
Boost.Thread v2
1.50.0から v1は提供されず v2に完全移行(予定)C++11 complianceが中心Move semanticsが Boost.MoveベースになるBoost.DateTimeベースの this_thread::sleepに加え
Boost.Chronoベースの this_thread::sleep_(for|until)
Boost.Thread v3
1.55.0から v3へ移行予定1.50.0 から 1.54.0は v3への移行期間#define BOOST_THREAD_VERSION 3
マクロで互換性等を細かく設定できるe.g. #define BOOST_THREAD_DONT_PROVIDE_FUTURE
ifdef → boost::unique_future
else → boost::future
カーネルスレッドとユーザースレッド
スケジューリングを誰がやるかが一番のポイントカーネルスレッドは知らないうちに実行コンテキストが変わっているユーザースレッドはプログラマが明示的にコンテキストスイッチを仕込まなければならない
ユーザースレッド単体では並行しかできないが、カーネルスレッドは並列に動作する可能性高いユーザースレッドのコンテキストスイッチは自分でアセンブリを書く必要がある
Boost.Context
ユーザーレベルでのコンテキストスイッチ機構を提供するライブラリ作者はOliver Kowalke
http://ok73.ok.funpic.de/boost/libs/context/doc/html/
現在サポートされている triple
上記ドキュメントの Tested Platformsの項を参考にする/trunk/libs/context/src/asm/ にあるファイルから推測する
Boost.Context
mini-review時点ではコンテキストに関する高級な処理があったが trunkに入ってから殆どの機能が削られたe.g. context linking / stack unwinding / high-level API
1.50.0のリリースには含まれないことになりそう現に release branchからは削除されている
ctx::fcontext_t orig, fc;
void f(intptr_t){ std::cout << "in context" << std::endl; ctx::jump_fcontext(&fc, &orig, 0);}
int main(){ const size_t ctxsize = ctx::default_stacksize();
ctx::stack_allocator alloc; fc.fc_stack.base = alloc.allocate(ctxsize); fc.fc_stack.limit = static_cast<char *>(fc.fc_stack.base) - ctxsize; ctx::make_fcontext(&fc, f);
std::cout << "pre jump" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "post jump" << std::endl;
alloc.deallocate(fc.fc_stack.base, ctxsize);}
ちょっとした注意
TLSは使ってはいけない多分 SEGVとかそういう話だと思う
スタックのデアロケートを自動化しようとコンテキスト内で開放してはいけないDeallocateが戻ると SEGV
コンテキスト内ではスコープを意識して使わないと dtorが呼ばれないコードになってしまうエントリーポイントをそのまま出ると exitが呼ばれる
ユーザースレッドの目的
カーネルスレッドはコンテキストスイッチのコストが非常に大きいスケジューリングする必要がある場合一連の流れを細切れにしてコールバックとかにするより処理の流れがわかりやすい
IOレイテンシの隠蔽
ユーザースレッドの目的
カーネルスレッドはコンテキストスイッチのコストが非常に大きいスケジューリングする必要がある場合一連の流れを細切れにしてコールバックとかにするより処理の流れがわかりやすい
IOレイテンシの隠蔽
今回はこれを例にとる
簡単な例
一定間隔でタイマイベントが発生する毎回少しづつ変化させたい例えば 3回で 1周
後々全く違う処理を同じイベントで行うかもしれないから callbackのインターフェースは共通化して vectorとかに突っ込みたい
正直こんな例だとあんまり利点を見いだせないかも ...
int state = 0;void timer_handler(){ switch (state) { case 0: ... process step 0 ... break; case 1: ... process step 1 ... break; case 2: ... process step 2 ... break; } state = (state + 1) % 3;}
function<void()> callback_timer;callback_timer = timer_handler;
while ((event = peek_event()) != FINISH){ switch (event) { ... snip ...
case TIMER: callback_timer(); break;
... snip ... }}
例えばコールバックベースだと
呼ばれる毎に共有できる形でどこかに
stateを保持しないといけな
い
timer_handlerを複数登録できるようにするには ...
void timer_handler(shared_ptr<int> ps){ switch (*ps) { case 0: ... process step 0 ... break; case 1: ... process step 1 ... break; case 2: ... process step 2 ... break; } *ps = (*ps + 1) % 3;}
vector<function<void()>> timer_callbacks;… snip...timer_callbacks.push_back( bind(timer_handler, make_shared<int>(0));… snip...while ((event = peek_event()) != FINISH){ switch (event) { ... snip ... case TIMER: for (auto &c : timer_callbacks) c(); break; ... snip ... }}
例えばコールバックベースだと
shared_ptr等で固有の
状態を持ち回る必要が
ユーザースレッドを導入するとfcontext_t fctx;// stackとか make_fcontextとか
vector<fcontext_t> timer_callbacks;… snip...timer_callbacks.push_back(move(fctx));
while ((event = peek_event()) != FINISH){ switch (event) { ... snip ... case TIMER: for (auto &ctx : timer_callbacks) jump_fcontext(&fo, &ctx, &ctx); break; ... snip ... }}
void timer_handler(fcontext_t *ctx){ while (true) { ... process step 0 … ctx = jump_fcontext(ctx, &fo, 0);
... process step 1 … ctx = jump_fcontext(ctx, &fo, 0);
... process step 2 … ctx = jump_fcontext(ctx, &fo, 0); }}
シングルスレッドの処理の
流れと同じように書ける
固有の状態はコンテキスト
自身が持ってるのでコンテキストの管理だけ
行えば良い
高級な実装
正直Boost.Contextは抽象化されてるとはいえ常用するには低級すぎる現在Oliverが Boost.Contextをベースとした
Boost.Coroutineを提案して Formal Review待ちhttp://ok73.ok.funpic.de/boost/libs/coroutine/doc/html/index.html
GSoC2006で Eric Nieblerが書いた Boost.Coroutineと基本インターフェースは同じ
ctx::fcontext_t orig, fc;
void f(intptr_t){ std::cout << "in context (pre jump)" << std::endl; ctx::jump_fcontext(&fc, &orig, 0); std::cout << "in context (post jump)" << std::endl; ctx::jump_fcontext(&fc, &orig, 0);}
int main(){ const size_t ctxsize = ctx::default_stacksize();
ctx::stack_allocator alloc; fc.fc_stack.base = alloc.allocate(ctxsize); fc.fc_stack.limit = static_cast<char *>(fc.fc_stack.base) - ctxsize; ctx::make_fcontext(&fc, f);
std::cout << "pre jump" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "now yield" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "post jump" << std::endl;
alloc.deallocate(fc.fc_stack.base, ctxsize);}
Boost.Context
最後に jumpを忘れてはいけないし、トップレベルの dtorは呼ばれない
typedef coro::coroutine<void()> coro_t;
void f(coro_t::self &self){ std::cout << "in coroutine (pre yield)" << std::endl; self.yeild(); std::cout << "in coroutine (post yield)" << std::endl;}
int main(){ coro_t c(f);
std::cout << "pre jump" << std::endl; c(); std::cout << “now yield” << std::endl; c(); std::cout << "post jump" << std::endl;}
Boost.Coroutine
最後に yieldを呼ぶ必要はないし dtorも呼ばれる
C++標準委員会での動き
Study Group 1
並行と並列に関する Study Group
n3328 Resumable Functions
言語仕様としてユーザースレッドを導入する提案resumableと awaitの 2つの contextual keywords
n3356 C++ Mutable Threads
Mutable Threads の提案
n3361 C++ Language Constructs for Parallel Programming
Intel® CilkTM Plus の紹介 (提案は次のmeetingらしい
C++標準委員会での動き
n3378 A preliminary proposal for work executors
Workerを投げると適当にスケジューリングして裏で実行するexecuterの提案
Workerの戻り値は投げた時に返ってくる futureで取得
しかしこれらが実際に入るのは早くとも C++2x頃なのでは
まとめ
Boost.Contextがコンテキストスイッチを抽象化しているのでアセンブリとか知らなくても何か遊べそう実際のプロダクトに使用されると面白そう
C++標準でも並行・並列の関心は高まっているが、投入はまだ先
就職先を考えねばならない