Boost.Asioの初心者なので、 io_service::run()
と混同されています。このメソッドがブロック/ブロック解除するときに誰かが私に説明できれば、私はそれを感謝します。ドキュメントの状態:
run()
関数は、すべての作業が終了してディスパッチするハンドラがなくなるまで、またはio_service
が停止するまでブロックします。複数のスレッドが
run()
関数を呼び出して、io_service
がハンドラーを実行できるスレッドのプールを設定できます。プールで待機しているすべてのスレッドは同等であり、io_service
はハンドラーを呼び出すためにそれらのいずれかを選択できます。
run()
関数の通常の終了は、io_service
オブジェクトが停止していることを意味します(stopped()
関数はtrueを返します)。run()
、run_one()
、poll()
、またはpoll_one()
への後続の呼び出しは、reset()
への前の呼び出しがない限り、すぐに戻ります。
次の文はどういう意味ですか?
[...]ディスパッチされるハンドラはもうありません[...]
io_service::run()
の動作を理解しようとすると、これに遭遇しました 例 (例3a)。その中で、io_service->run()
がブロックされ、作業指示を待機していることがわかります。
// WorkerThread invines io_service->run()
void WorkerThread(boost::shared_ptr<boost::asio::io_service> io_service);
void CalculateFib(size_t);
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service);
boost::shared_ptr<boost::asio::io_service::work> work(
new boost::asio::io_service::work(*io_service));
// ...
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}
io_service->post( boost::bind(CalculateFib, 3));
io_service->post( boost::bind(CalculateFib, 4));
io_service->post( boost::bind(CalculateFib, 5));
work.reset();
worker_threads.join_all();
ただし、私が取り組んでいた次のコードでは、クライアントはTCP/IPを使用して接続し、runメソッドはデータが非同期に受信されるまでブロックします。
typedef boost::asio::ip::tcp tcp;
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service);
boost::shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));
// Connect to 127.0.0.1:9100.
tcp::resolver resolver(*io_service);
tcp::resolver::query query("127.0.0.1",
boost::lexical_cast< std::string >(9100));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
socket->connect(endpoint_iterator->endpoint());
// Just blocks here until a message is received.
socket->async_receive(boost::asio::buffer(buf_client, 3000), 0,
ClientReceiveEvent);
io_service->run();
// Write response.
boost::system::error_code ignored_error;
std::cout << "Sending message \n";
boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error);
以下の2つの例でその動作を説明するrun()
の説明は歓迎します。
簡単な例から始めて、関連するBoost.Asioの部分を調べてみましょう。
void handle_async_receive(...) { ... }
void print() { ... }
...
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
...
io_service.post(&print); // 1
socket.connect(endpoint); // 2
socket.async_receive(buffer, &handle_async_receive); // 3
io_service.post(&print); // 4
io_service.run(); // 5
handlerはコールバックにすぎません。サンプルコードには、3つのハンドラーがあります。
print
ハンドラー(1)。handle_async_receive
ハンドラー(3)。print
ハンドラー(4)。同じprint()
関数が2回使用されますが、各使用は独自の一意に識別可能なハンドラーを作成すると見なされます。ハンドラーは、上記のような基本的な関数から、boost::bind()
やlambdasから生成されるファンクターなどのより複雑な構成体に至るまで、さまざまな形とサイズで提供されます。複雑さにかかわらず、ハンドラーはコールバック以外の何物でもありません。
作業とは、アプリケーションコードの代わりにBoost.Asioに要求された処理のことです。 Boost.Asioは、通知された直後に一部の作業を開始する場合もあれば、後の時点で作業を待機する場合もあります。作業が完了すると、Boost.Asioは提供されたhandlerを呼び出すことでアプリケーションに通知します。
Boost.Asioは、handlersが現在run()
、run_one()
、poll()
、またはpoll_one()
を呼び出しているスレッド内でのみ実行されることを保証します。これらは動作し、handlersを呼び出すスレッドです。したがって、上記の例では、io_service
(1)にポストされるときにprint()
は呼び出されません。代わりに、io_service
に追加され、後で呼び出されます。この場合、io_service.run()
(5)内にあります。
非同期操作 は作業を作成し、Boost.Asioはhandlerを呼び出して、作業が完了したときにアプリケーションに通知します。非同期操作は、名前に接頭辞async_
が付いた関数を呼び出すことにより作成されます。これらの関数は、開始関数とも呼ばれます。
非同期操作は、次の3つの固有のステップに分解できます。
io_service
を開始、または通知する必要があります。 async_receive
操作(3)は、ソケットからデータを非同期で読み取る必要があることをio_service
に通知し、async_receive
はすぐに戻ります。socket
がデータを受信すると、バイトが読み取られ、buffer
にコピーされます。実際の作業は次のいずれかで行われます。io_service
(5)を明示的に実行するとき。handle_async_receive
の呼び出し ReadHandler 。繰り返しますが、handlersはio_service
を実行しているスレッド内でのみ呼び出されます。したがって、作業がいつ完了するか(3または5)に関係なく、handle_async_receive()
はio_service.run()
(5)内でのみ呼び出されることが保証されます。これら3つのステップ間の時間と空間の分離は、制御フロー反転として知られています。非同期プログラミングを困難にする複雑さの1つです。ただし、 coroutines を使用するなど、これを軽減するのに役立つテクニックがあります。
io_service.run()
は何をしますか?スレッドがio_service.run()
を呼び出すと、workとhandlersがこのスレッド内から呼び出されます。上記の例では、io_service.run()
(5)は次の状態になるまでブロックします。
print
ハンドラーから呼び出されて返され、成功または失敗して受信操作が完了し、handle_async_receive
ハンドラーが呼び出されて返されました。io_service
は、 io_service::stop()
を介して明示的に停止されます。1つの潜在的な擬似的なフローは、次のように説明できます。
io_serviceの作成 ソケットの作成 io_serviceへの印刷ハンドラーの追加(1) ソケットの接続待機(2) 非同期読み取り作業の追加io_serviceへのリクエスト(3) 印刷ハンドラをio_serviceに追加(4) io_serviceを実行(5) 作業またはハンドラがありますか? はい、あります1つの作業と2つのハンドラー はソケットにデータがありますか?いいえ、何もしません run print handler(1) 作業またはハンドラーがありますか? はい、1つの作業があり、1つのハンドラーがあります。いいえ、何もしません printハンドラーを実行します(4) 作業またはハンドラーはありますか? はい、1つの作業があります ソケットにはデータがありますか?いいえ、待機を続けます -ソケットはデータを受信します- ソケットにはデータがあり、それをバッファに読み込みます handle_async_receiveハンドラーをio_service に追加しますか? はい、1つのハンドラーがあります run handle_async_receive handler(3) 作業またはハンドラーがありますか? いいえ、io_serviceを停止として設定して戻ります
読み取りが終了すると、io_service
に別のhandlerが追加されることに注意してください。この微妙な詳細は、非同期プログラミングの重要な機能です。 handlersを連鎖させることができます。たとえば、handle_async_receive
が期待したすべてのデータを取得しなかった場合、その実装は別の非同期読み取り操作をポストする可能性があり、その結果io_service
により多くの作業があるため、io_service.run()
から戻りません。
io_service
が機能しなくなった場合、アプリケーションは reset()
io_service
を実行してから再実行する必要があることに注意してください。
次に、質問で参照されている2つのコードを調べます。
socket->async_receive
は、io_service
に作業を追加します。したがって、io_service->run()
は、読み取り操作が成功またはエラーで完了し、ClientReceiveEvent
の実行が完了するか例外がスローされるまでブロックします。
わかりやすくするために、ここに小さな注釈付きの例3aを示します。
void CalculateFib(std::size_t n);
int main()
{
boost::asio::io_service io_service;
boost::optional<boost::asio::io_service::work> work = // '. 1
boost::in_place(boost::ref(io_service)); // .'
boost::thread_group worker_threads; // -.
for(int x = 0; x < 2; ++x) // :
{ // '.
worker_threads.create_thread( // :- 2
boost::bind(&boost::asio::io_service::run, &io_service) // .'
); // :
} // -'
io_service.post(boost::bind(CalculateFib, 3)); // '.
io_service.post(boost::bind(CalculateFib, 4)); // :- 3
io_service.post(boost::bind(CalculateFib, 5)); // .'
work = boost::none; // 4
worker_threads.join_all(); // 5
}
高レベルでは、プログラムはio_service
のイベントループを処理する2つのスレッドを作成します(2)。これにより、フィボナッチ数を計算する単純なスレッドプールが作成されます(3)。
質問コードとこのコードの大きな違いの1つは、このコードがio_service::run()
(2)beforeを呼び出し、実際の作業とハンドラーがio_service
(3)に追加されることです。 io_service::run()
がすぐに返されないように、 io_service::work
オブジェクトが作成されます(1)。このオブジェクトは、io_service
の作業不足を防ぎます。したがって、io_service::run()
は、作業の結果として返されません。
全体的な流れは次のとおりです。
io_service::work
に追加されたio_service
オブジェクトを作成して追加します。io_service::run()
を呼び出すスレッドプールが作成されました。 io_service
オブジェクトのため、これらのワーカースレッドはio_service::work
から戻りません。io_service
に追加し、すぐに戻ります。メインスレッドではなく、ワーカースレッドがこれらのハンドラーの実行をすぐに開始する場合があります。io_service::work
オブジェクトを削除します。io_service
にはハンドラも作業も含まれていないため、これは3つのハンドラすべてが実行を終了した後にのみ発生します。コードは、ハンドラーがio_service
に追加され、io_service
イベントループが処理される元のコードと同じ方法で、異なる方法で記述できます。これにより、io_service::work
を使用する必要がなくなり、次のコードが生成されます。
int main()
{
boost::asio::io_service io_service;
io_service.post(boost::bind(CalculateFib, 3)); // '.
io_service.post(boost::bind(CalculateFib, 4)); // :- 3
io_service.post(boost::bind(CalculateFib, 5)); // .'
boost::thread_group worker_threads; // -.
for(int x = 0; x < 2; ++x) // :
{ // '.
worker_threads.create_thread( // :- 2
boost::bind(&boost::asio::io_service::run, &io_service) // .'
); // :
} // -'
worker_threads.join_all(); // 5
}
問題のコードは非同期操作を使用していますが、非同期操作の完了を待機しているため、事実上同期的に機能しています。
socket.async_receive(buffer, handler)
io_service.run();
以下と同等です:
boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);
一般的な経験則として、同期操作と非同期操作を混在させないようにしてください。多くの場合、複雑なシステムを複雑なシステムに変えることができます。これは、非同期プログラミングの利点を強調する answer であり、その一部はBoost.Asioでもカバーされています documentation 。
run
の機能を単純化するために、紙の山を処理しなければならない従業員と考えてください。 1枚のシートを取り、シートが伝えることを行い、シートを捨てて、次のシートを取ります。彼がシーツを使い果たすと、オフィスを出ます。各シートには、パイルに新しいシートを追加するなど、あらゆる種類の指示があります。 asioに戻る:io_service
に2つの方法で作業を与えることができます。基本的には、リンクしたサンプルのようにpost
を使用するか、post
を内部で呼び出す他のオブジェクトを使用しますio_service
およびsocket
およびそのasync_*
メソッドと同様。