Mais conteúdo relacionado
Semelhante a Pub/Sub model, msm, and asio (20)
Mais de Takatoshi Kondo (9)
Pub/Sub model, msm, and asio
- 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 参加予定
- 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の順序とは異なる
- 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導入後
受信時の処理は
アクションに移動
イベントの遅延が可能
イベントを処理すると
現在状態に応じた
アクションが実行される
- 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
並行処理が可能
排他ロック 共有ロック
- 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を返送した後に、この処理が実行されることがある
- 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 でロックするのと何が違うのか?
- 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する必要がある