7

現在の状況

私は現在、単一のスレッドからメソッドio_serviceを呼び出す単一のオブジェクトを使用するboost.asioを使用してTCPサーバーを実装しました。run

これまでのところ、サーバーは必要なすべての情報をメモリに保持していたため、クライアントの要求にすぐに応答できました (受信ハンドラーで長時間実行される操作は必要ありませんでした)。

問題

現在、要件が変更されており、クライアントへの応答を作成するために、(ODBC を使用して) データベースから情報を取得する必要があります。これは、基本的に長時間実行されるブロック操作です。

いくつかのアプローチがありますが、どれが最適かはわかりません (さらに多くのアプローチがある可能性があります)。

最初のアプローチ

長時間実行される操作をハンドラーに保持し、io_service.run()複数のスレッドから単純に呼び出すことができました。使用可能な CPU コアと同じ数のスレッドを使用すると思いますか?

このアプローチは簡単に実装できますが、スレッドの数が限られているため、このアプローチで最高のパフォーマンスが得られるとは思いません (データベース アクセスは I/O バウンド操作であるため、スレッドはほとんどの時間アイドル状態です)。コンピューティング バウンド操作より)。

第二のアプローチ

このドキュメントのセクション 6では、次のように述べています。

長時間実行されるタスクにはスレッドを使用する

シングル スレッド設計の変形であるこの設計では、プロトコル ロジックの実装に単一の io_service::run() スレッドを使用します。長時間実行またはブロックしているタスクはバックグラウンド スレッドに渡され、完了すると、結果が io_service::run() スレッドに戻されます。

これは有望に思えますが、それを実装する方法がわかりません。このアプローチのコードスニペット/例を提供できる人はいますか?

第三のアプローチ

Boris Schäling は、boost イントロダクションのセクション 7.5 で、カスタム サービスを使用して boost.asio を拡張する方法を説明しています。

これは大変な作業のように見えます。このアプローチには、他のアプローチと比較して利点がありますか?

4

2 に答える 2

16

これらのアプローチは、明示的に相互に排他的ではありません。最初と 2 番目の組み合わせをよく見かけます。

  • 1 つ以上のスレッドが 1 つの でネットワーク I/O を処理していますio_service
  • 長時間実行またはブロックしているタスクは、別の に投稿されますio_service。これio_serviceは、ネットワーク I/O を処理するスレッドに干渉しないスレッド プールとして機能します。または、長時間実行またはブロックするタスクが必要になるたびに、切り離されたスレッドを生成することもできます。ただし、スレッドの作成/破棄のオーバーヘッドが顕著な影響を与える可能性があります。

スレッドプールの実装を提供するこの回答。さらに、これは 2 つの 間の相互作用を強調しようとする基本的な例ですio_services

#include <iostream>

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>

/// @brief Background service will function as a thread-pool where
///        long-standing blocking operations may occur without affecting
///        the network event loop.
boost::asio::io_service background_service;

/// @brief The main io_service will handle network operations.
boost::asio::io_service io_service;

boost::optional<boost::asio::io_service::work> work;

/// @brief ODBC blocking operation.
///
/// @brief data Data to use for query.
/// @brief handler Handler to invoke upon completion of operation.
template <typename Handler>
void query_odbc(unsigned int data,
                Handler handler)
{
  std::cout << "in background service, start querying odbc\n";
  std::cout.flush();
  // Mimic busy work.
  boost::this_thread::sleep_for(boost::chrono::seconds(5));

  std::cout << "in background service, posting odbc result to main service\n";
  std::cout.flush();
  io_service.post(boost::bind(handler, data * 2));
}

/// @brief Functions as a continuation for handle_read, that will be
///        invoked with results from ODBC.
void handle_read_odbc(unsigned int result)
{
  std::stringstream stream;
  stream << "in main service, got " << result << " from odbc.\n";
  std::cout << stream.str();
  std::cout.flush();

  // Allow io_service to stop in this example.
  work = boost::none;
}

/// @brief Mocked up read handler that will post work into a background
///        service.
void handle_read(const boost::system::error_code& error,
                 std::size_t bytes_transferred)
{
  std::cout << "in main service, need to query odbc" << std::endl;
  typedef void (*handler_type)(unsigned int);
  background_service.post(boost::bind(&query_odbc<handler_type>,
    21,                // data
    &handle_read_odbc) // handler
  );

  // Keep io_service event loop running in this example.
  work = boost::in_place(boost::ref(io_service));
} 

/// @brief Loop to show concurrency.
void print_loop(unsigned int iteration)
{
  if (!iteration) return;

  std::cout << "  in main service, doing work.\n";
  std::cout.flush();
  boost::this_thread::sleep_for(boost::chrono::seconds(1));
  io_service.post(boost::bind(&print_loop, --iteration));  
}

int main()
{
  boost::optional<boost::asio::io_service::work> background_work(
      boost::in_place(boost::ref(background_service)));

  // Dedicate 3 threads to performing long-standing blocking operations.
  boost::thread_group background_threads;
  for (std::size_t i = 0; i < 3; ++i)
    background_threads.create_thread(
      boost::bind(&boost::asio::io_service::run, &background_service));

  // Post a mocked up 'handle read' handler into the main io_service.
  io_service.post(boost::bind(&handle_read,
    make_error_code(boost::system::errc::success), 0));

  // Post a mockup loop into the io_service to show concurrency.
  io_service.post(boost::bind(&print_loop, 5));  

  // Run the main io_service.
  io_service.run();

  // Cleanup background.
  background_work = boost::none;
  background_threads.join_all();
}

そして出力:

メイン サービスでは、odbc にクエリを実行する必要があります
  主なサービスで、仕事をしています。
バックグラウンド サービスで、odbc のクエリを開始します
  主なサービスで、仕事をしています。
  主なサービスで、仕事をしています。
  主なサービスで、仕事をしています。
  主なサービスで、仕事をしています。
バックグラウンド サービスで、odbc の結果をメイン サービスにポストする
メインサービスでは、odbc から 42 を取得しました。

io_serviceメインポストを処理する 1 つのスレッドがに作用し、 がブロックbackground_serviceしている間、そのイベント ループを処理し続けることに注意してください。background_servicebackground_service結果を取得すると、ハンドラーを main にポストしio_serviceます。

于 2013-07-15T13:32:56.120 に答える
1

サーバーには同じ長時間実行タスクがあります (ストレージを使用したレガシー プロトコル)。したがって、サーバーはサービスのブロックを回避するために 200 のスレッドを実行しています (はい、200 のスレッドが実行されていますio_service::run)。それほど素晴らしいことではありませんが、今のところうまく機能しています。

私たちが抱えていた唯一の問題はasio::strand、hadler が現在呼び出されているときにロックされる、いわゆる「実装」を使用することです。このストランド バケットを増やし、io_service::postストランド ラップを使用せずに「取り外し」タスクを実行することで、これを解決しました。

一部のタスクは数秒または数分実行される場合がありますが、現時点では問題なく動作します。

于 2013-07-15T18:53:06.983 に答える