web-dev-qa-db-ja.com

boost :: asioを使用するときに接続ごとにストランドが必要なのはなぜですか?

BoostのWebサイトで HTTP Server の例を確認しています。

接続ごとにstrandが必要な理由を説明してください。ご覧のとおり、read_some read-eventのハンドラーのみ。だから基本的に read_some呼び出しはシーケンシャルであるため、ストランドは必要ありません(そして 番目の段落の項目2 は同じことを言います)。マルチスレッド環境のリスクはどこにありますか?

45
expert

ドキュメントは正しいです。 HTTP Server などの半二重プロトコル実装では、strandは不要です。呼び出しチェーンは次のように説明できます。

_void connection::start()
{
  socket.async_receive_from(..., &handle_read);  ----.
}                                                    |
    .------------------------------------------------'
    |      .-----------------------------------------.
    V      V                                         |
void connection::handle_read(...)                    |
{                                                    |
  if (result)                                        |
    boost::asio::async_write(..., &handle_write); ---|--.
  else if (!result)                                  |  |
    boost::asio::async_write(..., &handle_write);  --|--|
  else                                               |  |
    socket_.async_read_some(..., &handle_read);  ----'  |
}                                                       |
    .---------------------------------------------------'
    |
    V
void handle_write(...)
_

図に示すように、パスごとに開始される非同期イベントは1つだけです。 _socket__でハンドラーまたは操作を同時に実行する可能性がないため、暗黙的なストランドで実行されていると言われます。


スレッドセーフ

この例では問題として取り上げられていませんが、 _boost::asio::async_write_ など、ストランドと合成された演算の1つの重要な詳細を強調したいと思います。詳細を説明する前に、まずBoost.Asioでスレッドセーフティモデルについて説明します。ほとんどのBoost.Asioオブジェクトでは、オブジェクトで複数の非同期操作を保留しても安全です。オブジェクトの同時呼び出しが安全でないことが指定されているだけです。以下の図では、各列がスレッドを表し、各線がスレッドが現時点で実行していることを表しています。

単一のスレッドが順次呼び出しを行っても、他のスレッドは順次呼び出しを行わなくても安全です。

 thread_1 | thread_2 
 -------------------------------------- + ----- ---------------------------------- 
 socket.async_receive(...); | ... 
 socket.async_write_some(...); | ...

複数のスレッドが呼び出しを行うことは安全ですが、同時には行われません。

 thread_1 | thread_2 
 -------------------------------------- + ----- ---------------------------------- 
 socket.async_receive(...); | ... 
 ... | socket.async_write_some(...);

ただし、複数のスレッドが同時に呼び出しを行うのは安全ではありません1

 thread_1 | thread_2 
 -------------------------------------- + ----- ---------------------------------- 
 socket.async_receive(...); | socket.async_write_some(...); 
 ... | ...

ストランド

同時呼び出しを防ぐために、ハンドラーはストランド内から呼び出されることがよくあります。これは、次のいずれかによって行われます。

  • _strand.wrap_ でハンドラーをラップします。これにより、ストランドを介してディスパッチする新しいハンドラーが返されます。
  • Posting または dispatching ストランドを介して直接。

構成された操作は、streamへの中間呼び出しがhandler構成された操作が開始されるストランドの代わりに、ストランド(存在する場合)。他の操作と比較すると、ストランドが指定されている場所が逆になります。以下は、ストランドの使用に焦点を当てたいくつかのサンプルコードです。これは、非構成操作を介して読み取られ、同時に構成操作で書き込まれるソケットを示します。

_void start()
{
  // Start read and write chains.  If multiple threads have called run on
  // the service, then they may be running concurrently.  To protect the
  // socket, use the strand.
  strand_.post(&read);
  strand_.post(&write);
}

// read always needs to be posted through the strand because it invokes a
// non-composed operation on the socket.
void read()
{
  // async_receive is initiated from within the strand.  The handler does
  // not affect the strand in which async_receive is executed.
  socket_.async_receive(read_buffer_, &handle_read);
}

// This is not running within a strand, as read did not wrap it.
void handle_read()
{
  // Need to post read into the strand, otherwise the async_receive would
  // not be safe.
  strand_.post(&read);
}

// The entry into the write loop needs to be posted through a strand.
// All intermediate handlers and the next iteration of the asynchronous write
// loop will be running in a strand due to the handler being wrapped.
void write()
{
  // async_write will make one or more calls to socket_.async_write_some.
  // All intermediate handlers (calls after the first), are executed
  // within the handler's context (strand_).
  boost::asio::async_write(socket_, write_buffer_,
                           strand_.wrap(&handle_write));
}

// This will be invoked from within the strand, as it was a wrapped
// handler in write().
void handle_write()
{
  // handler_write() is invoked within a strand, so write() does not
  // have to dispatched through the strand.
  write();
}
_

ハンドラータイプの重要性

また、Boost.Asioは、合成された操作内で 引数に依存する検索 (ADL)を使用して、完了ハンドラーのストランドを通じて中間ハンドラーを呼び出します。そのため、完了ハンドラーのタイプに適切な asio_handler_invoke() フックがあることが重要です。 _boost::function_が_strand.wrap_の戻り型から構築される場合など、適切なasio_handler_invoke()フックを持たない型に対して型消去が発生した場合、中間ハンドラーはストランドの外で実行され、完了ハンドラのみがストランド内で実行されます。詳細は this 回答を参照してください。

次のコードでは、すべての中間ハンドラーと完了ハンドラーがストランド内で実行されます。

_boost::asio::async_write(stream, buffer, strand.wrap(&handle_write));
_

次のコードでは、完了ハンドラのみがストランド内で実行されます。中間ハンドラーはいずれもストランド内で実行されません。

_boost::function<void()> handler(strand.wrap(&handle_write));
boost::asio::async_write(stream, buffer, handler);
_

1. 改訂履歴 は、このルールの異常を文書化します。 OSでサポートされている場合 同期 読み取り、書き込み、受け入れ、および接続操作はスレッドセーフです。完全を期すためにここに含めますが、注意して使用することをお勧めします。

91
Tanner Sansbury

それは、合成された操作 async_write が原因であると考えています。 async_writeは非同期で複数の socket :: async_write_some で構成されています。 Strandは、これらの操作をシリアル化するのに役立ちます。 asioの作者であるChris Kohlhoffは、彼の boostconトーク で1:17頃にそれについて簡単に話します。

8
Vikas