SlideShare una empresa de Scribd logo
1 de 42
Pub/Subモデルとmsmとasioと
Takatoshi Kondo
2016/7/23 1
発表内容
2016/7/23 2
• Pub/Subモデルとは?
• コネクションとスレッド
• 2つのスケーラビリティ
• brokerの状態管理とイベントの遅延処理
• msmの要求する排他制御
• io_serviceのpostと実行順序
• async_writeとstrand
自己紹介
2016/7/23 3
• 近藤 貴俊
• ハンドルネーム redboltz
• msgpack-cコミッタ
– https://github.com/msgpack/msgpack-c
• MQTTのC++クライアント mqtt_client_cpp
開発
– https://github.com/redboltz/mqtt_client_cpp
• MQTTを拡張したスケーラブルな
brokerを仕事で開発中
• CppCon 2016 参加予定
Pub/Subモデルとは
2016/7/23 4
topic A
publisher 1
subscriber 1
topic B
publisher 2
subscriber 2
hello
world
論理的な概念
subscribe
publish
world
client
Pub/Subモデルとは
2016/7/23 5
broker
clientpublisher subscriber
topictopic
connection
物理的?な配置
node
コネクションとスレッド
2016/7/23 6
broker
client
connection
worker
thread
worker
thread
worker
thread
client client
context switch のコスト増大
コネクションとスレッド
2016/7/23 7
broker
client
connection
boost::asio::io_service on 1 thread
client client
io_service
2016/7/23 8
#include <iostream>
#include <boost/asio.hpp>
int main() {
boost::asio::io_service ios;
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.run();
}
http://melpon.org/wandbox/permlink/MzfsrLNdJjfAeV15
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
11
12
様々な処理(ネットワーク、タイマ、シリアルポート、
シグナルハンドル、etc)をio_serviceにpost。
イベントが無くなるまで処理を実行
http://www.boost.org/doc/html/boost_asio/reference.html
io_service
2016/7/23 9
#include <iostream>
#include <boost/asio.hpp>
int main() {
boost::asio::io_service ios;
ios.post([&ios]{
std::cout << __LINE__ << std::endl;
ios.post([&ios]{
std::cout << __LINE__ << std::endl;
ios.post([&ios]{
std::cout << __LINE__ << std::endl;
});
});
});
ios.run();
}
http://melpon.org/wandbox/permlink/lXbFTVurVNUXM8BZ
7
9
11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
処理の中で次のリクエストをpost
2つのスケーラビリティ
2016/7/23 10
• マルチスレッド
• マルチノード(マルチサーバ)
マルチスレッドにスケールアウト
2016/7/23 11
broker
client
connection
boost::asio::io_service on 1 thread
client client
コアを有効活用したい
マルチスレッドにスケールアウト
2016/7/23 12
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
int main() {
boost::asio::io_service ios;
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
std::vector<std::thread> ths;
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
for (auto& t : ths) t.join();
std::cout << "finished" << std::endl;
}
http://melpon.org/wandbox/permlink/z5bQJYgO23tvM9XF
8
9
10
11
7
finished
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
実行順序はpostの順序とは異なる
マルチスレッドにスケールアウト
2016/7/23 13
broker
client
connection
client client
ios
client
thread threadthread
subscriber
Pub/Subモデルとロック
2016/7/23 14
subscriber
topic
publisher
subscribers_
subscribe
subscribe
subscribe
unsubscribe
排他ロック
publish
対象のsubscriberに配送
共有ロック
webserver
マルチノードにスケールアウト
2016/7/23 15
client client client
load balancer
webserver webserver
毎回コネクションを切断する、Webサーバなどは
スケールアウトがシンプル
broker
brokerbroker
マルチノードにスケールアウト
2016/7/23 16
client client client client
Pub/Subモデルはコネクション型通信のため、
Webサーバのようなリクエスト毎の切断を
前提とするロードバランス戦略をとれない
情報の転送が必要
publisher subscriber
load balancer or dispatcher
hello
broker
brokerbroker
マルチノードにスケールアウト
2016/7/23 17
client client client client
ルーティングなどの
情報の同期が必要
publisher subscriber
同期中
publish/Defer
同期済み
publish/配信処理
同期完了
イベント処理の遅延
ステートマシンが常に必須とは限らないが、
今回は必要であると仮定する。
msmとasioの組み合わせ
2016/7/23 18
boost::asio::async_read(
socket_,
boost::asio::buffer(payload_),
[this](
boost::system::error_code const& ec,
std::size_t bytes_transferred){
// error checking ...
// 受信時の処理
}
);
boost::shared_lock<mutex> guard(mtx_subscribers_);
auto& idx = subscribers_.get<tag_topic>();
auto r = idx.equal_range(topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
boost::asio::write(socket, boost::asio::buffer(payload_));
}
全てのsubscriberに対して
publish内容を配信
msm導入前
msmとasioの組み合わせ
2016/7/23 19
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < s_normal, e_pub, msmf::none, a_pub, msmf::none >,
msmf::Row < s_sync, e_pub, msmf::none, msmf::Defer, msmf::none >
> {};
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
boost::asio::write(socket, boost::asio::buffer(e.payload));
}
}
};
// boost::asio::async_read ハンドラ内にて
process_event(e_pub(topic, payload));
msm導入後
受信時の処理は
アクションに移動
イベントの遅延が可能
イベントを処理すると
現在状態に応じた
アクションが実行される
msmとスレッド
2016/7/23 20
process_event()の呼び出しはserializeされなければならない
msmとスレッド
2016/7/23 21
同期中
publish/Defer
同期済み
publish/配信処理
同期完了
process_event()の呼び出しはserializeされなければならない
複数のスレッドで同時に状態遷移が起こると、
msmの内部状態がおかしくなるのであろう
// boost::asio::async_read ハンドラ内にて
process_event(e_pub(topic, payload)); ここに排他ロックが必要となる
subscribersubscriberpublish受信 subscriber
subscribersubscriberpublish受信 subscriber
配信
配信
別々の受信でも順番に処理せねばならない
msmとスレッド
2016/7/23 22
排他ロック
共有ロック
排他ロック
共有ロック
msmとasioの組み合わせ
2016/7/23 23
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
boost::asio::write(socket, boost::asio::buffer(e.payload));
}
});
}
};
排他ロックの必要な範囲では、ios.post()のみ行い、
ios.post()に渡した処理が呼び出されるところで、
共有ロックを行う
subscribersubscriberpublish受信 subscriber
subscribersubscriberpublish受信 subscriber
post
post
postのみserialize 並行処理が可能
msmとasioの組み合わせ
2016/7/23 24
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
boost::asio::write(socket, boost::asio::buffer(e.payload));
}
});
}
};
排他ロックの必要な範囲では、ios.post()のみ行い、
ios.post()に渡した処理が呼び出されるところで、
共有ロックを行う
注意点
・処理の遅延に問題は無いか?
・ios.post()に渡した処理が参照するオブジェクトは生存しているか?
forループの処理もpostすれば。。。
2016/7/23 25
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
ios.post([&socket, e]{
boost::asio::write(socket, boost::asio::buffer(e.payload));
});
}
});
}
};
ループの中で行われるwrite()が並列化され、パフォーマンスの向上が見込める
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
ios.post([&socket, e]{
boost::asio::write(socket, boost::asio::buffer(e.payload));
});
}
});
}
};
forループの処理もpostすれば。。。
2016/7/23 26
publish受信 subscriber
post
postのみserialize
並行処理が可能
post
subscriber
subscriber
publish受信 subscriber
post
並行処理が可能
post
subscriber
subscriber
並行処理が可能
排他ロック 共有ロック
broker
forループの処理もpostすれば。。。
2016/7/23 27
client
client
publisher
subscriber
1. subscribe
2. ack
3. publish(data)
4. data
1と3がほぼ同時に発生した場合、subscriberから見て許容される振る舞いは、
2, 4の順で受信 (1が3よりも先にbrokerで処理された場合)
または
2のみ受信 (1が3よりも後にbrokerで処理された場合)
4, 2の順で受信が発生してはならない。 (ackの前にdata到着)
broker
forループの処理もpostすれば。。。
2016/7/23 28
client
client
publisher
subscriber
1. unsubscribe
2. data
3. publish(data)
4. ack
1と3がほぼ同時に発生した場合、subscriberから見て許容される振る舞いは、
2, 4の順で受信 (1が3よりも先にbrokerで処理された場合)
または
4のみ受信 (1が3よりも後にbrokerで処理された場合)
4, 2の順で受信が発生してはならない。 (ackの後にdata到着)
forループの処理もpostすれば。。。
2016/7/23 29
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
int main() {
boost::asio::io_service ios;
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
ios.post([]{ std::cout << __LINE__ << std::endl; });
std::vector<std::thread> ths;
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
ths.emplace_back([&ios]{ ios.run(); });
for (auto& t : ths) t.join();
std::cout << "finished" << std::endl;
}
http://melpon.org/wandbox/permlink/z5bQJYgO23tvM9XF
8
9
10
11
7
finished
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
実行順序はpostの順序とは異なる
forループの処理もpostすれば。。。
2016/7/23 30
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
ios.post([&socket, e]{
boost::asio::write(socket, boost::asio::buffer(e.payload));
});
}
});
}
};
unsubscribe処理を行い、ackを返送した後に、この処理が実行されることがある
問題はどこにあるのか?
2016/7/23 31
• 同一コネクションに対する送信の順序を
保証したいが、
• io_service::post()を使うことで、順序の保証が
できなくなっている
• しかし、ループ処理の並列化は行いたい
• コネクションとの対応付けを考慮した、
処理のpostが行えれば良い
boost::asio::async_write
2016/7/23 32
現実的には、このハンドラ内で次のasync_writeを呼ぶことになる
boost::asio::async_write
2016/7/23 33
template <typename F>
void my_async_write(
std::shared_ptr<std::string> const& buf,
F const& func) {
strand_.post(
[this, buf, func]
() {
queue_.emplace_back(buf, func);
if (queue_.size() > 1) return;
my_async_write_imp();
}
);
}
まずenque
データは、バッファと完了ハンドラ
未完了のasync_writeがあるなら
何もせず終了
async_writeの呼び出し処理
制約無く、いつでも呼べる、async_writeを作るには、
自前でキューイングなどの処理を実装する必要がある。
boost::asio::async_write
2016/7/23 34
void my_async_write_imp() {
auto& elem = queue_.front();
auto const& func = elem.handler();
as::async_write(
socket_,
as::buffer(elem.ptr(), elem.size()),
strand_.wrap(
[this, func]
(boost::system::error_code const& ec,
std::size_t bytes_transferred) {
func(ec);
queue_.pop_front();
if (!queue_.empty()) {
my_async_write_imp();
}
}
)
);
}
queueからデータを取り出して、
async_write
まだqueueにデータがあれば、
再びasync_write
queueからデータを消去し
strand_.post() および strand_.wrap() を用いて、
排他制御を行っている
queue_ だけ mutex でロックするのと何が違うのか?
async_readもstrand wrapする
2016/7/23 35
boost::asio::async_read(
socket_,
boost::asio::buffer(payload_),
strand_.wrap(
[this](
boost::system::error_code const& ec,
std::size_t bytes_transferred){
// error checking ...
// 受信時の処理
}
)
);
async_readもstrand経由で処理する
strandは本当に必要か?
2016/7/23 36
strandしなくても、暗黙的にstrandになるケース
publish処理
2016/7/23 37
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ);
}
});
}
};
自前の非同期writeを呼び出す
subscribe / unsubscribe の ack送信処理も、同様に、
自前の非同期writeを経由させることで、順序の入れ替わりを
防ぎ、かつ、処理の並列化を実現することができる
publish処理
2016/7/23 38
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ);
}
});
}
};
自前の非同期writeを呼び出す
publish受信 subscriber
post
postのみserialize
並行処理が可能
かつ
同一接続に対しては
シリアライズ
my_async_write
subscriber
subscriber
publish受信 subscriber
post
subscriber
subscriber
並行処理が可能
排他ロック 共有ロック
my_async_write
並行処理が可能
かつ
同一接続に対しては
シリアライズ
publish処理
2016/7/23 39
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ);
}
});
}
};
自前の非同期writeを呼び出す
非同期writeは十分に軽量であるため、forループの所要時間は短かった。
排他ロックの中で処理を行ってもパフォーマンスは落ちなかった。
よってシンプルな実装を採用した。(グレーの部分のコードを削除した)
publish処理
2016/7/23 40
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ);
}
});
}
};
自前の非同期writeを呼び出す
publish受信 subscriber
post
postのみserialize
並行処理が可能
かつ
同一接続に対しては
シリアライズ
my_async_write
subscriber
subscriber
publish受信 subscriber
post
subscriber
subscriber
並行処理が可能
排他ロック 共有ロック
my_async_write
並行処理が可能
かつ
同一接続に対しては
シリアライズ
publish処理
2016/7/23 41
struct a_pub {
template <typename Event, typename Fsm, typename Source, typename Target>
void operator()(Event const& e, Fsm& f, Source&, Target&) const {
ios.post([&f, e]{
boost::shared_lock<mutex> guard(f.mtx_subscribers_);
auto& idx = f.subscribers_.get<tag_topic>();
auto r = idx.equal_range(e.topic);
for (; r.first != r.second; ++r.first) {
auto& socket = r.first->socket;
socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ);
}
});
}
};
自前の非同期writeを呼び出す
publish受信 subscriber
my_async_writeのみserialize
並行処理が可能
かつ
同一接続に対しては
シリアライズ
my_async_write
subscriber
subscriber
publish受信 subscriber
subscriber
subscriber
排他ロック
my_async_write
並行処理が可能
かつ
同一接続に対しては
シリアライズ
まとめ
2016/7/23 42
• io_serviceを複数スレッドでrun()することで、
コアを有効利用できる
• msmのDeferはイベント処理を遅延できて便利
• その一方、msmの状態遷移は排他制御を要求する
• post()を利用することで任意の処理を、
遅延でき、ロックの最適化が可能となる
• post()はコネクションを意識しないので、
マルチスレッドの場合、実行順序が保証されない
• 通信では同一コネクションに対して、
順序を保証したいことがよくある
• そんなときは、async_write()が使える
• 好きなタイミングで呼べるasync_write()は
自分で実装する必要がある
• キューイング処理とasync_writeハンドラに加え、
async_read()も合わせてstrandする必要がある

Más contenido relacionado

La actualidad más candente

C#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsC#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsYoshifumi Kawai
 
HTTP/2時代のウェブサイト設計
HTTP/2時代のウェブサイト設計HTTP/2時代のウェブサイト設計
HTTP/2時代のウェブサイト設計Kazuho Oku
 
WebRTC getStats - WebRTC Meetup Tokyo 5 LT
WebRTC getStats - WebRTC Meetup Tokyo 5 LTWebRTC getStats - WebRTC Meetup Tokyo 5 LT
WebRTC getStats - WebRTC Meetup Tokyo 5 LTmganeko
 
WebRTC meetup Tokyo 1
WebRTC meetup  Tokyo 1WebRTC meetup  Tokyo 1
WebRTC meetup Tokyo 1mganeko
 
Node.js with WebRTC DataChannel
Node.js with WebRTC DataChannelNode.js with WebRTC DataChannel
Node.js with WebRTC DataChannelmganeko
 
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようWebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようmganeko
 
Reactive Extensions v2.0
Reactive Extensions v2.0Reactive Extensions v2.0
Reactive Extensions v2.0Yoshifumi Kawai
 
Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方
Observable Everywhere  - Rxの原則とUniRxにみるデータソースの見つけ方Observable Everywhere  - Rxの原則とUniRxにみるデータソースの見つけ方
Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方Yoshifumi Kawai
 
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2azumakuniyuki 🐈
 
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~Recruit Technologies
 
Pd Kai#3 Startup Process
Pd Kai#3 Startup ProcessPd Kai#3 Startup Process
Pd Kai#3 Startup Processnagachika t
 
Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察貴仁 大和屋
 
linq.js - Linq to Objects for JavaScript
linq.js - Linq to Objects for JavaScriptlinq.js - Linq to Objects for JavaScript
linq.js - Linq to Objects for JavaScriptYoshifumi Kawai
 

La actualidad más candente (20)

Rust-DPDK
Rust-DPDKRust-DPDK
Rust-DPDK
 
C#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsC#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive Extensions
 
HTTP/2時代のウェブサイト設計
HTTP/2時代のウェブサイト設計HTTP/2時代のウェブサイト設計
HTTP/2時代のウェブサイト設計
 
WebRTC getStats - WebRTC Meetup Tokyo 5 LT
WebRTC getStats - WebRTC Meetup Tokyo 5 LTWebRTC getStats - WebRTC Meetup Tokyo 5 LT
WebRTC getStats - WebRTC Meetup Tokyo 5 LT
 
Rake
RakeRake
Rake
 
NanoStrand
NanoStrandNanoStrand
NanoStrand
 
WebRTC meetup Tokyo 1
WebRTC meetup  Tokyo 1WebRTC meetup  Tokyo 1
WebRTC meetup Tokyo 1
 
Node.js with WebRTC DataChannel
Node.js with WebRTC DataChannelNode.js with WebRTC DataChannel
Node.js with WebRTC DataChannel
 
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようWebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
 
qmake入門
qmake入門qmake入門
qmake入門
 
Reactive Extensions v2.0
Reactive Extensions v2.0Reactive Extensions v2.0
Reactive Extensions v2.0
 
Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方
Observable Everywhere  - Rxの原則とUniRxにみるデータソースの見つけ方Observable Everywhere  - Rxの原則とUniRxにみるデータソースの見つけ方
Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方
 
HTTP/2, QUIC入門
HTTP/2, QUIC入門HTTP/2, QUIC入門
HTTP/2, QUIC入門
 
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2
JSONでメール送信 | HTTP API Server ``Haineko''/YAPC::Asia Tokyo 2013 LT Day2
 
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~
Node.jsv0.8からv4.xへのバージョンアップ ~大規模Push通知基盤の運用事例~
 
Pd Kai#3 Startup Process
Pd Kai#3 Startup ProcessPd Kai#3 Startup Process
Pd Kai#3 Startup Process
 
Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察Gocon2017:Goのロギング周りの考察
Gocon2017:Goのロギング周りの考察
 
Rust-DPDK
Rust-DPDKRust-DPDK
Rust-DPDK
 
LINQ in Unity
LINQ in UnityLINQ in Unity
LINQ in Unity
 
linq.js - Linq to Objects for JavaScript
linq.js - Linq to Objects for JavaScriptlinq.js - Linq to Objects for JavaScript
linq.js - Linq to Objects for JavaScript
 

Destacado

C++14 solve explicit_default_constructor
C++14 solve explicit_default_constructorC++14 solve explicit_default_constructor
C++14 solve explicit_default_constructorAkira Takahashi
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるHideyuki Tanaka
 
Boost.Timer
Boost.TimerBoost.Timer
Boost.Timermelpon
 
Introduction to boost test
Introduction to boost testIntroduction to boost test
Introduction to boost testKohsuke Yuasa
 

Destacado (6)

Glfw3,OpenGL,GUI
Glfw3,OpenGL,GUI Glfw3,OpenGL,GUI
Glfw3,OpenGL,GUI
 
C++14 solve explicit_default_constructor
C++14 solve explicit_default_constructorC++14 solve explicit_default_constructor
C++14 solve explicit_default_constructor
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISる
 
Boost.Timer
Boost.TimerBoost.Timer
Boost.Timer
 
C++14 enum hash
C++14 enum hashC++14 enum hash
C++14 enum hash
 
Introduction to boost test
Introduction to boost testIntroduction to boost test
Introduction to boost test
 

Similar a Pub/Sub model, msm, and asio

Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力ThinReports
 
gumiStudy#7 The MessagePack Project
gumiStudy#7 The MessagePack ProjectgumiStudy#7 The MessagePack Project
gumiStudy#7 The MessagePack ProjectSadayuki Furuhashi
 
Microsoft Graph API Library for Go
Microsoft Graph API Library for GoMicrosoft Graph API Library for Go
Microsoft Graph API Library for Goyaegashi
 
20130315 abc firefox_os
20130315 abc firefox_os20130315 abc firefox_os
20130315 abc firefox_osTomoaki Konno
 
API Gateway + LambdaでLINE通知サービス構築
API Gateway + LambdaでLINE通知サービス構築API Gateway + LambdaでLINE通知サービス構築
API Gateway + LambdaでLINE通知サービス構築Ken'ichirou Kimura
 
mod_auth_ticket - Bringing Single-Sign-On to lighttpd
mod_auth_ticket - Bringing Single-Sign-On to lighttpdmod_auth_ticket - Bringing Single-Sign-On to lighttpd
mod_auth_ticket - Bringing Single-Sign-On to lighttpdTaisuke Yamada
 
OCamlのトップレベルあれそれ
OCamlのトップレベルあれそれOCamlのトップレベルあれそれ
OCamlのトップレベルあれそれnomaddo
 
社内向けTech Talk資料~Fluentdの基本紹介~
社内向けTech Talk資料~Fluentdの基本紹介~ 社内向けTech Talk資料~Fluentdの基本紹介~
社内向けTech Talk資料~Fluentdの基本紹介~ Daisuke Ikeda
 
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用Shintaro Fukushima
 
About GStreamer 1.0 application development for beginners
About GStreamer 1.0 application development for beginnersAbout GStreamer 1.0 application development for beginners
About GStreamer 1.0 application development for beginnersShota TAMURA
 
Continuation with Boost.Context
Continuation with Boost.ContextContinuation with Boost.Context
Continuation with Boost.ContextAkira Takahashi
 
恐怖!シェルショッカーの POSIX原理主義シェルスクリプト
恐怖!シェルショッカーの POSIX原理主義シェルスクリプト恐怖!シェルショッカーの POSIX原理主義シェルスクリプト
恐怖!シェルショッカーの POSIX原理主義シェルスクリプトRichie Shellshoccar
 
Python physicalcomputing
Python physicalcomputingPython physicalcomputing
Python physicalcomputingNoboru Irieda
 

Similar a Pub/Sub model, msm, and asio (20)

Prosym2012
Prosym2012Prosym2012
Prosym2012
 
Boost tour 1_40_0
Boost tour 1_40_0Boost tour 1_40_0
Boost tour 1_40_0
 
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
 
Inside Movable Type
Inside Movable TypeInside Movable Type
Inside Movable Type
 
Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7
 
Gumi study7 messagepack
Gumi study7 messagepackGumi study7 messagepack
Gumi study7 messagepack
 
gumiStudy#7 The MessagePack Project
gumiStudy#7 The MessagePack ProjectgumiStudy#7 The MessagePack Project
gumiStudy#7 The MessagePack Project
 
Microsoft Graph API Library for Go
Microsoft Graph API Library for GoMicrosoft Graph API Library for Go
Microsoft Graph API Library for Go
 
20130315 abc firefox_os
20130315 abc firefox_os20130315 abc firefox_os
20130315 abc firefox_os
 
API Gateway + LambdaでLINE通知サービス構築
API Gateway + LambdaでLINE通知サービス構築API Gateway + LambdaでLINE通知サービス構築
API Gateway + LambdaでLINE通知サービス構築
 
mod_auth_ticket - Bringing Single-Sign-On to lighttpd
mod_auth_ticket - Bringing Single-Sign-On to lighttpdmod_auth_ticket - Bringing Single-Sign-On to lighttpd
mod_auth_ticket - Bringing Single-Sign-On to lighttpd
 
OCamlのトップレベルあれそれ
OCamlのトップレベルあれそれOCamlのトップレベルあれそれ
OCamlのトップレベルあれそれ
 
boost tour 1.48.0 all
boost tour 1.48.0 allboost tour 1.48.0 all
boost tour 1.48.0 all
 
社内向けTech Talk資料~Fluentdの基本紹介~
社内向けTech Talk資料~Fluentdの基本紹介~ 社内向けTech Talk資料~Fluentdの基本紹介~
社内向けTech Talk資料~Fluentdの基本紹介~
 
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用
統計解析言語Rにおける大規模データ管理のためのboost.interprocessの活用
 
About GStreamer 1.0 application development for beginners
About GStreamer 1.0 application development for beginnersAbout GStreamer 1.0 application development for beginners
About GStreamer 1.0 application development for beginners
 
Continuation with Boost.Context
Continuation with Boost.ContextContinuation with Boost.Context
Continuation with Boost.Context
 
恐怖!シェルショッカーの POSIX原理主義シェルスクリプト
恐怖!シェルショッカーの POSIX原理主義シェルスクリプト恐怖!シェルショッカーの POSIX原理主義シェルスクリプト
恐怖!シェルショッカーの POSIX原理主義シェルスクリプト
 
Python physicalcomputing
Python physicalcomputingPython physicalcomputing
Python physicalcomputing
 
Boost Tour 1.50.0 All
Boost Tour 1.50.0 AllBoost Tour 1.50.0 All
Boost Tour 1.50.0 All
 

Más de Takatoshi Kondo

Effective Modern C++ study group Item39
Effective Modern C++ study group Item39Effective Modern C++ study group Item39
Effective Modern C++ study group Item39Takatoshi Kondo
 
MessagePack(msgpack): Compact and Fast Serialization Library
MessagePack(msgpack): Compact and Fast Serialization LibraryMessagePack(msgpack): Compact and Fast Serialization Library
MessagePack(msgpack): Compact and Fast Serialization LibraryTakatoshi Kondo
 
Boostsapporomsmpost 111106070819-phpapp02
Boostsapporomsmpost 111106070819-phpapp02Boostsapporomsmpost 111106070819-phpapp02
Boostsapporomsmpost 111106070819-phpapp02Takatoshi Kondo
 
Boostsapporomsmpre 111030054504-phpapp02
Boostsapporomsmpre 111030054504-phpapp02Boostsapporomsmpre 111030054504-phpapp02
Boostsapporomsmpre 111030054504-phpapp02Takatoshi Kondo
 
Unpack mechanism of the msgpack-c
Unpack mechanism of the msgpack-cUnpack mechanism of the msgpack-c
Unpack mechanism of the msgpack-cTakatoshi Kondo
 
Aho-Corasick string matching algorithm
Aho-Corasick string matching algorithmAho-Corasick string matching algorithm
Aho-Corasick string matching algorithmTakatoshi Kondo
 

Más de Takatoshi Kondo (9)

Effective Modern C++ study group Item39
Effective Modern C++ study group Item39Effective Modern C++ study group Item39
Effective Modern C++ study group Item39
 
MessagePack(msgpack): Compact and Fast Serialization Library
MessagePack(msgpack): Compact and Fast Serialization LibraryMessagePack(msgpack): Compact and Fast Serialization Library
MessagePack(msgpack): Compact and Fast Serialization Library
 
Emcpp0506
Emcpp0506Emcpp0506
Emcpp0506
 
Boostsapporomsmpost 111106070819-phpapp02
Boostsapporomsmpost 111106070819-phpapp02Boostsapporomsmpost 111106070819-phpapp02
Boostsapporomsmpost 111106070819-phpapp02
 
Boostsapporomsmpre 111030054504-phpapp02
Boostsapporomsmpre 111030054504-phpapp02Boostsapporomsmpre 111030054504-phpapp02
Boostsapporomsmpre 111030054504-phpapp02
 
Unpack mechanism of the msgpack-c
Unpack mechanism of the msgpack-cUnpack mechanism of the msgpack-c
Unpack mechanism of the msgpack-c
 
N3495 inplace realloc
N3495 inplace reallocN3495 inplace realloc
N3495 inplace realloc
 
N3701 concept lite
N3701 concept liteN3701 concept lite
N3701 concept lite
 
Aho-Corasick string matching algorithm
Aho-Corasick string matching algorithmAho-Corasick string matching algorithm
Aho-Corasick string matching algorithm
 

Pub/Sub model, msm, and asio

  • 2. 発表内容 2016/7/23 2 • Pub/Subモデルとは? • コネクションとスレッド • 2つのスケーラビリティ • brokerの状態管理とイベントの遅延処理 • msmの要求する排他制御 • io_serviceのpostと実行順序 • async_writeとstrand
  • 3. 自己紹介 2016/7/23 3 • 近藤 貴俊 • ハンドルネーム redboltz • msgpack-cコミッタ – https://github.com/msgpack/msgpack-c • MQTTのC++クライアント mqtt_client_cpp 開発 – https://github.com/redboltz/mqtt_client_cpp • MQTTを拡張したスケーラブルな brokerを仕事で開発中 • CppCon 2016 参加予定
  • 4. Pub/Subモデルとは 2016/7/23 4 topic A publisher 1 subscriber 1 topic B publisher 2 subscriber 2 hello world 論理的な概念 subscribe publish world
  • 8. io_service 2016/7/23 8 #include <iostream> #include <boost/asio.hpp> int main() { boost::asio::io_service ios; ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.run(); } http://melpon.org/wandbox/permlink/MzfsrLNdJjfAeV15 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 11 12 様々な処理(ネットワーク、タイマ、シリアルポート、 シグナルハンドル、etc)をio_serviceにpost。 イベントが無くなるまで処理を実行 http://www.boost.org/doc/html/boost_asio/reference.html
  • 9. io_service 2016/7/23 9 #include <iostream> #include <boost/asio.hpp> int main() { boost::asio::io_service ios; ios.post([&ios]{ std::cout << __LINE__ << std::endl; ios.post([&ios]{ std::cout << __LINE__ << std::endl; ios.post([&ios]{ std::cout << __LINE__ << std::endl; }); }); }); ios.run(); } http://melpon.org/wandbox/permlink/lXbFTVurVNUXM8BZ 7 9 11 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 処理の中で次のリクエストをpost
  • 12. マルチスレッドにスケールアウト 2016/7/23 12 #include <iostream> #include <thread> #include <boost/asio.hpp> int main() { boost::asio::io_service ios; ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); std::vector<std::thread> ths; ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); for (auto& t : ths) t.join(); std::cout << "finished" << std::endl; } http://melpon.org/wandbox/permlink/z5bQJYgO23tvM9XF 8 9 10 11 7 finished 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 実行順序はpostの順序とは異なる
  • 15. webserver マルチノードにスケールアウト 2016/7/23 15 client client client load balancer webserver webserver 毎回コネクションを切断する、Webサーバなどは スケールアウトがシンプル
  • 16. broker brokerbroker マルチノードにスケールアウト 2016/7/23 16 client client client client Pub/Subモデルはコネクション型通信のため、 Webサーバのようなリクエスト毎の切断を 前提とするロードバランス戦略をとれない 情報の転送が必要 publisher subscriber load balancer or dispatcher hello
  • 17. broker brokerbroker マルチノードにスケールアウト 2016/7/23 17 client client client client ルーティングなどの 情報の同期が必要 publisher subscriber 同期中 publish/Defer 同期済み publish/配信処理 同期完了 イベント処理の遅延 ステートマシンが常に必須とは限らないが、 今回は必要であると仮定する。
  • 18. msmとasioの組み合わせ 2016/7/23 18 boost::asio::async_read( socket_, boost::asio::buffer(payload_), [this]( boost::system::error_code const& ec, std::size_t bytes_transferred){ // error checking ... // 受信時の処理 } ); boost::shared_lock<mutex> guard(mtx_subscribers_); auto& idx = subscribers_.get<tag_topic>(); auto r = idx.equal_range(topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; boost::asio::write(socket, boost::asio::buffer(payload_)); } 全てのsubscriberに対して publish内容を配信 msm導入前
  • 19. msmとasioの組み合わせ 2016/7/23 19 struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < s_normal, e_pub, msmf::none, a_pub, msmf::none >, msmf::Row < s_sync, e_pub, msmf::none, msmf::Defer, msmf::none > > {}; struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; boost::asio::write(socket, boost::asio::buffer(e.payload)); } } }; // boost::asio::async_read ハンドラ内にて process_event(e_pub(topic, payload)); msm導入後 受信時の処理は アクションに移動 イベントの遅延が可能 イベントを処理すると 現在状態に応じた アクションが実行される
  • 21. msmとスレッド 2016/7/23 21 同期中 publish/Defer 同期済み publish/配信処理 同期完了 process_event()の呼び出しはserializeされなければならない 複数のスレッドで同時に状態遷移が起こると、 msmの内部状態がおかしくなるのであろう // boost::asio::async_read ハンドラ内にて process_event(e_pub(topic, payload)); ここに排他ロックが必要となる subscribersubscriberpublish受信 subscriber subscribersubscriberpublish受信 subscriber 配信 配信 別々の受信でも順番に処理せねばならない
  • 23. msmとasioの組み合わせ 2016/7/23 23 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; boost::asio::write(socket, boost::asio::buffer(e.payload)); } }); } }; 排他ロックの必要な範囲では、ios.post()のみ行い、 ios.post()に渡した処理が呼び出されるところで、 共有ロックを行う subscribersubscriberpublish受信 subscriber subscribersubscriberpublish受信 subscriber post post postのみserialize 並行処理が可能
  • 24. msmとasioの組み合わせ 2016/7/23 24 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; boost::asio::write(socket, boost::asio::buffer(e.payload)); } }); } }; 排他ロックの必要な範囲では、ios.post()のみ行い、 ios.post()に渡した処理が呼び出されるところで、 共有ロックを行う 注意点 ・処理の遅延に問題は無いか? ・ios.post()に渡した処理が参照するオブジェクトは生存しているか?
  • 25. forループの処理もpostすれば。。。 2016/7/23 25 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; ios.post([&socket, e]{ boost::asio::write(socket, boost::asio::buffer(e.payload)); }); } }); } }; ループの中で行われるwrite()が並列化され、パフォーマンスの向上が見込める
  • 26. struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; ios.post([&socket, e]{ boost::asio::write(socket, boost::asio::buffer(e.payload)); }); } }); } }; forループの処理もpostすれば。。。 2016/7/23 26 publish受信 subscriber post postのみserialize 並行処理が可能 post subscriber subscriber publish受信 subscriber post 並行処理が可能 post subscriber subscriber 並行処理が可能 排他ロック 共有ロック
  • 27. broker forループの処理もpostすれば。。。 2016/7/23 27 client client publisher subscriber 1. subscribe 2. ack 3. publish(data) 4. data 1と3がほぼ同時に発生した場合、subscriberから見て許容される振る舞いは、 2, 4の順で受信 (1が3よりも先にbrokerで処理された場合) または 2のみ受信 (1が3よりも後にbrokerで処理された場合) 4, 2の順で受信が発生してはならない。 (ackの前にdata到着)
  • 28. broker forループの処理もpostすれば。。。 2016/7/23 28 client client publisher subscriber 1. unsubscribe 2. data 3. publish(data) 4. ack 1と3がほぼ同時に発生した場合、subscriberから見て許容される振る舞いは、 2, 4の順で受信 (1が3よりも先にbrokerで処理された場合) または 4のみ受信 (1が3よりも後にbrokerで処理された場合) 4, 2の順で受信が発生してはならない。 (ackの後にdata到着)
  • 29. forループの処理もpostすれば。。。 2016/7/23 29 #include <iostream> #include <thread> #include <boost/asio.hpp> int main() { boost::asio::io_service ios; ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); ios.post([]{ std::cout << __LINE__ << std::endl; }); std::vector<std::thread> ths; ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); ths.emplace_back([&ios]{ ios.run(); }); for (auto& t : ths) t.join(); std::cout << "finished" << std::endl; } http://melpon.org/wandbox/permlink/z5bQJYgO23tvM9XF 8 9 10 11 7 finished 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 実行順序はpostの順序とは異なる
  • 30. forループの処理もpostすれば。。。 2016/7/23 30 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; ios.post([&socket, e]{ boost::asio::write(socket, boost::asio::buffer(e.payload)); }); } }); } }; unsubscribe処理を行い、ackを返送した後に、この処理が実行されることがある
  • 31. 問題はどこにあるのか? 2016/7/23 31 • 同一コネクションに対する送信の順序を 保証したいが、 • io_service::post()を使うことで、順序の保証が できなくなっている • しかし、ループ処理の並列化は行いたい • コネクションとの対応付けを考慮した、 処理のpostが行えれば良い
  • 33. boost::asio::async_write 2016/7/23 33 template <typename F> void my_async_write( std::shared_ptr<std::string> const& buf, F const& func) { strand_.post( [this, buf, func] () { queue_.emplace_back(buf, func); if (queue_.size() > 1) return; my_async_write_imp(); } ); } まずenque データは、バッファと完了ハンドラ 未完了のasync_writeがあるなら 何もせず終了 async_writeの呼び出し処理 制約無く、いつでも呼べる、async_writeを作るには、 自前でキューイングなどの処理を実装する必要がある。
  • 34. boost::asio::async_write 2016/7/23 34 void my_async_write_imp() { auto& elem = queue_.front(); auto const& func = elem.handler(); as::async_write( socket_, as::buffer(elem.ptr(), elem.size()), strand_.wrap( [this, func] (boost::system::error_code const& ec, std::size_t bytes_transferred) { func(ec); queue_.pop_front(); if (!queue_.empty()) { my_async_write_imp(); } } ) ); } queueからデータを取り出して、 async_write まだqueueにデータがあれば、 再びasync_write queueからデータを消去し strand_.post() および strand_.wrap() を用いて、 排他制御を行っている queue_ だけ mutex でロックするのと何が違うのか?
  • 35. async_readもstrand wrapする 2016/7/23 35 boost::asio::async_read( socket_, boost::asio::buffer(payload_), strand_.wrap( [this]( boost::system::error_code const& ec, std::size_t bytes_transferred){ // error checking ... // 受信時の処理 } ) ); async_readもstrand経由で処理する
  • 37. publish処理 2016/7/23 37 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ); } }); } }; 自前の非同期writeを呼び出す subscribe / unsubscribe の ack送信処理も、同様に、 自前の非同期writeを経由させることで、順序の入れ替わりを 防ぎ、かつ、処理の並列化を実現することができる
  • 38. publish処理 2016/7/23 38 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ); } }); } }; 自前の非同期writeを呼び出す publish受信 subscriber post postのみserialize 並行処理が可能 かつ 同一接続に対しては シリアライズ my_async_write subscriber subscriber publish受信 subscriber post subscriber subscriber 並行処理が可能 排他ロック 共有ロック my_async_write 並行処理が可能 かつ 同一接続に対しては シリアライズ
  • 39. publish処理 2016/7/23 39 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ); } }); } }; 自前の非同期writeを呼び出す 非同期writeは十分に軽量であるため、forループの所要時間は短かった。 排他ロックの中で処理を行ってもパフォーマンスは落ちなかった。 よってシンプルな実装を採用した。(グレーの部分のコードを削除した)
  • 40. publish処理 2016/7/23 40 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ); } }); } }; 自前の非同期writeを呼び出す publish受信 subscriber post postのみserialize 並行処理が可能 かつ 同一接続に対しては シリアライズ my_async_write subscriber subscriber publish受信 subscriber post subscriber subscriber 並行処理が可能 排他ロック 共有ロック my_async_write 並行処理が可能 かつ 同一接続に対しては シリアライズ
  • 41. publish処理 2016/7/23 41 struct a_pub { template <typename Event, typename Fsm, typename Source, typename Target> void operator()(Event const& e, Fsm& f, Source&, Target&) const { ios.post([&f, e]{ boost::shared_lock<mutex> guard(f.mtx_subscribers_); auto& idx = f.subscribers_.get<tag_topic>(); auto r = idx.equal_range(e.topic); for (; r.first != r.second; ++r.first) { auto& socket = r.first->socket; socket.my_async_write(boost::asio::buffer(e.payload), 完了ハンドラ); } }); } }; 自前の非同期writeを呼び出す publish受信 subscriber my_async_writeのみserialize 並行処理が可能 かつ 同一接続に対しては シリアライズ my_async_write subscriber subscriber publish受信 subscriber subscriber subscriber 排他ロック my_async_write 並行処理が可能 かつ 同一接続に対しては シリアライズ
  • 42. まとめ 2016/7/23 42 • io_serviceを複数スレッドでrun()することで、 コアを有効利用できる • msmのDeferはイベント処理を遅延できて便利 • その一方、msmの状態遷移は排他制御を要求する • post()を利用することで任意の処理を、 遅延でき、ロックの最適化が可能となる • post()はコネクションを意識しないので、 マルチスレッドの場合、実行順序が保証されない • 通信では同一コネクションに対して、 順序を保証したいことがよくある • そんなときは、async_write()が使える • 好きなタイミングで呼べるasync_write()は 自分で実装する必要がある • キューイング処理とasync_writeハンドラに加え、 async_read()も合わせてstrandする必要がある