99

Boost.Asio の初心者なので、 と混乱していio_service::run()ます。このメソッドがいつブロック/ブロック解除されるかを誰かが説明してくれれば幸いです。ドキュメントには次のように記載されています。

関数はrun()、すべての作業が終了してディスパッチするハンドラーがなくなるまで、またはio_serviceが停止するまでブロックされます。

複数のスレッドがこの関数を呼び出して、がハンドラを実行できるrun()スレッドのプールを設定できます。io_serviceプールで待機しているすべてのスレッドは同等であり、io_serviceそれらのいずれかを選択してハンドラーを呼び出すことができます。

関数からの通常の終了は、オブジェクトが停止run()したことを意味します (関数は true を返します)。、、またはへの後続の呼び出しは、 への事前の呼び出しがない限り、すぐに戻ります。io_servicestopped()run()run_one()poll()poll_one()reset()

次のステートメントはどういう意味ですか?

[...] ディスパッチされるハンドラーはもうありません [...]


の動作を理解しようとしているときに、この(例 3a)io_service::run()に出会いました。その中で、ブロックして作業指示を待っていることがわかります。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);

run()以下の 2 つの例でその動作を説明する説明をいただければ幸いです。

4

2 に答える 2

259

財団

簡単な例から始めて、関連する 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

ハンドラーとは?

ハンドラーは単なるコールバックにすぎません。サンプル コードには、3 つのハンドラがあります。

  • printハンドラー (1) 。
  • handle_async_receiveハンドラー (3) 。
  • printハンドラー (4) 。

同じprint()関数が 2 回使用されたとしても、それぞれの使用は、独自の一意に識別可能なハンドラーを作成すると見なされます。ハンドラーには、上記のような基本的な関数からboost::bind()、ラムダから生成されたファンクターなどのより複雑な構造に至るまで、さまざまな形状とサイズがあります。複雑さに関係なく、ハンドラーは依然としてコールバックにすぎません。

仕事とは?

作業は、Boost.Asio がアプリケーション コードに代わって行うように要求された処理です。Boost.Asio は、通知されるとすぐに一部の作業を開始する場合もあれば、後で作業を行うために待機する場合もあります。作業が完了すると、Boost.Asio は提供されたhandlerを呼び出してアプリケーションに通知します。

Boost.Asioは、現在、、、またはを呼び出しているスレッド内でのみハンドラーが実行されることを保証します。これらは、作業を行い、ハンドラを呼び出すスレッドです。したがって、上記の例では、 (1)にポストされたときに呼び出されません。代わりに、に追加され、後で呼び出されます。この場合は(5)以内です。run()run_one()poll()poll_one()print()io_serviceio_serviceio_service.run()

非同期操作とは

非同期操作は作業を作成し、Boost.Asio はハンドラーを呼び出して、作業が完了したときにアプリケーションに通知します。非同期操作は、名前に接頭辞が付いた関数を呼び出すことによって作成されますasync_これらの関数は、開始関数とも呼ばれます。

非同期操作は、次の 3 つの固有の手順に分解できます。

  • 関連する作業を開始または通知io_serviceする必要があります。async_receive操作 (3) は、ソケットからデータを非同期に読み取る必要があることを に通知し、すぐio_serviceasync_receive戻ります。
  • 実際の作業を行います。この場合、 がsocketデータを受信すると、バイトが読み取られて にコピーされbufferます。実際の作業は次のいずれかで行われます。
    • Boost.Asio がブロックしないと判断できる場合は、開始関数 (3)。
    • アプリケーションがio_service(5) を明示的に実行する場合。
  • handle_async_receive ReadHandlerの呼び出し。繰り返しになりますが、ハンドラーは、 を実行しているスレッド内でのみ呼び出されますio_service。したがって、作業がいつ完了したかに関係なく (3 または 5)、 (5)handle_async_receive()内でのみ呼び出されることが保証されますio_service.run()

これら 3 つのステップ間の時間と空間の分離は、制御フローの反転として知られています。これは、非同期プログラミングを難しくしている複雑さの 1 つです。ただし、コルーチンを使用するなど、これを軽減するのに役立つ手法があります。

何をしますio_service.run()か?

スレッドが を呼び出すとio_service.run()、このスレッド内から作業とハンドラーが呼び出されます。上記の例では、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 つのハンドラーがあります
      ソケットにはデータがありますか? いいえ、何もしません
      印刷ハンドラーを実行する (1)
  仕事やハンドラーはありますか?
    はい、1 つのワークと 1 つのハンドラーがあります
      ソケットにはデータがありますか? いいえ、何もしません
      印刷ハンドラーを実行する (4)
  仕事やハンドラーはありますか?
    はい、1作品あります
      ソケットにはデータがありますか? いいえ、待ち続けます
  -- ソケットがデータを受信 --
      ソケットにデータがあり、バッファに読み込みます
      handle_async_receive ハンドラーを io_service に追加します
  仕事やハンドラーはありますか?
    はい、ハンドラーが 1 つあります
      handle_async_receive ハンドラを実行する (3)
  仕事やハンドラーはありますか?
    いいえ、io_service を停止として設定し、戻ります

読み取りが終了すると、別のハンドラが に追加されることに注目してくださいio_service。この微妙な詳細は、非同期プログラミングの重要な機能です。ハンドラーを連鎖させることができます。たとえば、handle_async_receiveが予期したすべてのデータを取得しなかった場合、その実装は別の非同期読み取り操作を送信し、その結果、io_serviceより多くの作業が必要になり、 から返されない可能性がありio_service.run()ます。

io_serviceが機能しなくなった場合、アプリケーションは再度実行reset()するio_service前に を実行する必要があることに注意してください。


質問例と例 3a コード

それでは、質問で参照されている 2 つのコードを調べてみましょう。

質問コード

socket->async_receiveに作業を追加しますio_service。したがって、io_service->run()読み取り操作が成功またはエラーで完了するまでブロックされ、ClientReceiveEvent実行が終了するか、例外がスローされます。

例 3aコード

理解しやすくするために、注釈付きの例 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 つは、このコードが実際の作業とハンドラーが(3)に追加される前にio_service::run()(2)を呼び出すことです。がすぐに戻らないようにするために、オブジェクトが作成されます (1)。このオブジェクトは、が作業不足になるのを防ぎます。したがって、作業なしの結果として戻ることはありません。io_serviceio_service::run()io_service::workio_serviceio_service::run()

全体の流れは次のとおりです。

  1. io_service::workに追加するオブジェクトを作成して追加しますio_service
  2. を呼び出すスレッドプールが作成されましたio_service::run()。これらのワーカー スレッドは、オブジェクトがio_service原因で返されません。io_service::work
  3. フィボナッチ数を計算する 3 つのハンドラを に追加し、io_serviceすぐに戻ります。メイン スレッドではなく、ワーカー スレッドがこれらのハンドラの実行をすぐに開始する場合があります。
  4. オブジェクトを削除しio_service::workます。
  5. ワーカー スレッドの実行が完了するまで待ちます。これは、3 つのハンドラすべての実行が終了した後にのみ発生しますio_service

このコードは、元のコードと同じように別の方法で記述できます。この場合、ハンドラが に追加されio_serviceio_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);

一般的な経験則として、同期操作と非同期操作を混在させないようにしてください。多くの場合、複雑なシステムを複雑なシステムに変えることができます。この回答は、非同期プログラミングの利点を強調しています。その一部は、Boost.Asioドキュメントにも記載されています。

于 2013-03-22T16:48:01.787 に答える
20

仕組みを単純化するためrunに、紙の山を処理しなければならない従業員と考えてください。1 枚のシートを取り、そのシートが示すことを実行し、そのシートを捨てて次のシートを取ります。彼がシートを使い果たすと、オフィスを離れます。各シートには、新しいシートをパイルに追加するなど、あらゆる種類の指示を含めることができます。asio に戻りますio_service: 基本的に 2 つの方法で作業に与えることができます:リンクしたサンプルのように使用するか、およびそのメソッドのようにをpost内部的に呼び出す他のオブジェクトを使用します。postio_servicesocketasync_*

于 2013-03-22T10:40:28.200 に答える